JavaScript Data Types Explained: All 8 Types and the `typeof` Quirks

Link copied
JavaScript Data Types Explained: All 8 Types and the `typeof` Quirks

JavaScript Data Types Explained: All 8 Types and the `typeof` Quirks

JS Tutorial Module 1: Foundations Lesson 1.3

JavaScript has eight data types. Seven of them are primitives — simple, immutable values like numbers and strings. The eighth is objects — everything else, including arrays, functions, dates, maps, and your own classes. Understanding the split is the foundation for understanding the rest of the language: how variables behave, how equality works, how functions pass arguments, and why typeof null === 'object' is one of the longest-running bugs in software history.

This lesson walks through every type, what makes primitives different from objects, and the typeof rules you need to know to read any JavaScript codebase.

The eight types at a glance #

Category Types
Primitives (7) undefined, null, boolean, number, bigint, string, symbol
Objects (1) object (covers Array, Function, Date, Map, Set, class instances, …)

That's it. Every value you'll ever encounter in JavaScript is one of those eight categories. There is no separate "array" type at the language level — arrays are objects. There is no "function" type — functions are objects with a [[Call]] internal slot. The fancy stuff is layered on top of object.

Primitives: simple, immutable, passed by value #

A primitive is a single, indivisible value. It cannot have properties. It cannot be modified — only replaced.

undefined #

The value a variable has before you assign one. Also what a function returns if you don't explicitly return anything.

let x;
console.log(x); // undefined

function noop() {}
console.log(noop()); // undefined

null #

A value that explicitly means "nothing." You assign it when you want to represent an intentional absence.

let currentUser = null; // explicitly no user yet

The distinction between undefined (forgotten / never set) and null (intentionally empty) is a convention, not a hard rule. Most teams use undefined for "missing" and null for "deliberately empty." Modern JS leans on undefined more — null is largely a holdover from the language's earliest days.

boolean #

true or false. The whole story.

const isReady = true;
const hasErrors = false;

number #

The one numeric type for most cases. It's a 64-bit IEEE 754 floating-point value, the same format used in most languages for double.

const pi = 3.14;
const answer = 42;
const tiny = 1e-10;
const huge = 1.7976931348623157e+308; // Number.MAX_VALUE

This means there is no separate integer type. 1 and 1.0 are the same value. It also means floating-point gotchas apply: 0.1 + 0.2 is 0.30000000000000004, not 0.3. We cover that in Module 5.

Three special numeric values to know:

NaN          // "Not a Number" — result of e.g. 0/0 or Number('abc')
Infinity     // overflow or 1/0
-Infinity    // negative overflow

NaN has one infamous property: it is the only JavaScript value not equal to itself.

NaN === NaN; // false
Number.isNaN(NaN); // true — always use this to check for NaN

bigint #

For integers larger than 2^53 - 1 (the safe integer range of number). Created with the n suffix or BigInt(...).

const huge = 9007199254740993n;
const alsoHuge = BigInt('9007199254740993');
console.log(huge + 1n); // 9007199254740994n

You cannot mix bigint and number in arithmetic — it throws.

1n + 1; // TypeError: Cannot mix BigInt and other types

Useful for IDs from large-scale systems, financial calculations, cryptography. Not needed in most day-to-day code.

string #

A sequence of UTF-16 code units. Created with single quotes, double quotes, or backticks (the latter give you template literals with ${interpolation}).

const a = 'hello';
const b = "hello";
const c = `hello, ${a}`;

Strings are immutable. s.toUpperCase() returns a new string; it does not change s.

const s = 'hi';
s.toUpperCase(); // 'HI'
console.log(s);  // 'hi' — unchanged

symbol #

A unique, opaque identifier. Useful for property keys when you need them to be guaranteed unique (no collisions with other code).

const id = Symbol('user-id');
const id2 = Symbol('user-id');
console.log(id === id2); // false — every Symbol() call is unique

We go deep on symbols (well-known symbols, the global registry, why iterators use them) in Module 3.

Objects: composite, mutable, passed by reference #

Everything that isn't a primitive is an object. Arrays, functions, dates, regexes, maps, sets, plain objects, class instances — all object.

const plain = { name: 'Ada' };
const arr = [1, 2, 3];
const fn = () => {};
const date = new Date();
const map = new Map();

typeof plain; // 'object'
typeof arr;   // 'object'
typeof fn;    // 'function'  ← the one exception
typeof date;  // 'object'
typeof map;   // 'object'

Objects can hold properties. Their contents can be mutated. They are passed by reference — when you pass an object to a function and the function mutates it, the original is mutated.

function addPoints(user, n) {
  user.points += n;
}
const u = { points: 0 };
addPoints(u, 5);
console.log(u.points); // 5 — the original object was changed

Compare with primitives, which are passed by value:

function increment(n) {
  n = n + 1;
}
let x = 1;
increment(x);
console.log(x); // 1 — unchanged, n was a copy

This distinction is the source of many bugs. Module 3 covers it in detail.

The typeof operator: the practical truth table #

typeof is the most-used way to check what kind of value you have. Its rules look simple but include three notable gotchas.

Value typeof returns Notes
undefined 'undefined'
null 'object' The famous bug. Should be 'null'. Can't be fixed without breaking the web.
true / false 'boolean'
42, 3.14, NaN, Infinity 'number' Yes, typeof NaN === 'number'.
9n 'bigint'
'hi' 'string'
Symbol() 'symbol'
function() {} 'function' Special-cased — functions are objects but typeof distinguishes them.
{}, [], new Date(), etc. 'object' Everything else.

The three things to remember #

  1. typeof null === 'object' — a 30-year-old bug from JavaScript's first implementation. The early version stored type tags in the lower bits of values; null was represented as a null pointer (all-zero bits) which happened to look like the object tag. By the time anyone noticed, too much code on the web depended on it.
  2. typeof NaN === 'number'NaN is part of the IEEE 754 numeric system. It is a number — specifically, a number whose value couldn't be computed. Use Number.isNaN(x) to check for it.
  3. typeof of a function is 'function' — even though functions are objects under the hood. This special case exists because checking for callability is so common.

Distinguishing array from plain object #

typeof [] returns 'object'. To check for an actual array, use Array.isArray:

Array.isArray([]);            // true
Array.isArray({ length: 0 }); // false
Array.isArray('hi');          // false

Distinguishing null from an object likewise requires extra logic:

function isPlainObject(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v);
}

These helpers come up in almost every codebase. ESLint plugins like unicorn/no-typeof-undefined and TypeScript's type narrowing handle most of the same checks for you in larger projects.

Type conversion is its own topic #

JavaScript implicitly converts between types in many situations: '5' + 1, if (someObject), == comparisons. The rules are subtle and surprising — 1 == '1' is true, [] == ![] is true, and null == undefined is true but null === undefined is false.

We cover all of this in Module 6 (type-coercion-equality). For now, two simple rules will save you 90% of the pain:

  1. Use === and !==, never == and !=. The strict operators don't convert types; they just compare.
  2. Convert explicitly when you want to convert. Number(x), String(x), Boolean(x), parseInt(x, 10). Be the one who decides, not the engine.

A summary you can pin to the wall #

  • 8 types total. 7 primitives, 1 object category.
  • Primitives are passed by value, immutable, simple.
  • Objects are passed by reference, mutable, composite.
  • typeof returns one of 8 strings, plus the famous null → 'object' gotcha and the function → 'function' special case.
  • Always check arrays with Array.isArray, never with typeof.
  • Always check NaN with Number.isNaN, never with ===.

Everything else in JavaScript — the equality rules, the call-stack mechanics, the array methods, the class system — is built on top of these eight types.

What's next #

Lesson 1.4 covers operators: arithmetic, comparison, logical, bitwise, nullish coalescing, optional chaining, and the half-dozen modern operators that make JavaScript expression-heavy code surprisingly readable in 2026.

Try it yourself #

The quickest way to feel the primitive-vs-object split is the famous == puzzle. Try predicting the result before you peek:

YouPredict the output:
console.log(typeof null, typeof NaN, typeof []);
Claude · used js_sandboxIt logs object number object.

typeof null is 'object' — the 30-year-old bug.
typeof NaN is 'number' — NaN is a numeric value, just one that couldn’t be computed.
typeof [] is 'object' — arrays are a kind of object; use Array.isArray() to distinguish them.

Three values, three lessons, one line of code. That's the type system in action — quirks and all.

Up next in JavaScript

More from this topic

View all JavaScript articles →

Enjoyed this article?

Get new JavaScript tutorials delivered. No spam — just code-first articles when they ship.

Leave a Comment

Your email address will not be published. Required fields are marked *