03 — Data Types — Primitives vs Objects, and the typeof Quirks
"Numbers, strings, and objects" was my entire type model, and it explained none of the surprises. The idea that everything else hangs off: there are exactly two categories of value — primitives (copied by value) and objects (copied by reference) — and the seven primitive types are the only things on the primitive side. [1]
The framing that finally landed is the split, not the list. Most of JavaScript's type-related surprises — mutation bugs, typeof quirks, equality weirdness — collapse to one question: is this value a primitive or an object? Primitives are immutable and assigned/copied by value. Objects are mutable and assigned/copied by reference. Everything follows from there.
The seven primitives
Each primitive is a single, immutable value [1][2]:
- Number — one numeric type for both integers and floats, stored as IEEE 754 double-precision. Special values: Infinity, -Infinity, NaN. The "all numbers are floats" design is why 0.1 + 0.2 !== 0.3 [3].
- BigInt — for integers beyond Number.MAX_SAFE_INTEGER (2⁵³−1). Written with an n suffix: 9007199254740993n. Needed for crypto, large counters, exact integer math [4].
- String — a sequence of characters. Single quotes, double quotes, or backticks (template literals). Immutable — "editing" a string always creates a new one.
- Boolean — true or false. The trap is truthy and falsy: most values coerce to true, but 0, "", null, undefined, NaN, and false itself are falsy.
- null — intentional absence of value. The typeof quirk: typeof null === "object", a bug from the original implementation that can't be fixed without breaking the web [5].
- undefined — a variable declared but not assigned, or a function that returned nothing. The default "not set" sentinel.
- Symbol — a unique, immutable identifier, mainly used as hidden object property keys to avoid collisions [6].
The difference between null and undefined is one I used to blur: null means I deliberately put nothing here; undefined means nothing was ever set here. Checking === null versus === undefined is how I tell intent from absence.
Objects: the reference side
Anything not in the primitive list is an object — plain objects, arrays, functions, dates, regexps, maps, sets, errors. The defining trait isn't the shape; it's that they're passed by reference [7]:
const a = { count: 1 };
const b = a; // b points to the SAME object
b.count = 2;
console.log(a.count); // 2 — a sees the changeTwo names, one object. That's why mutating a passed-in argument inside a function changes the caller's data — a classic source of bugs. Primitives don't do this; assigning let x = 5; let y = x; copies the value, and changing y leaves x alone.
The typeof operator
typeof returns a string describing a value's type [8]. It's the quick type check, and it has two famous quirks worth memorizing:
Expression | Returns | Notes |
|---|---|---|
typeof 5 | "number" | |
typeof "hi" | "string" | |
typeof true | "boolean" | |
typeof undefined | "undefined" | |
typeof Symbol() | "symbol" | |
typeof 5n | "bigint" | |
typeof null | "object" | the famous bug |
typeof function(){} | "function" | functions are a subtype |
typeof {} | "object" | arrays, dates, regexps too |
The typeof null === "object" trap is why null checks use === null, never typeof. And typeof can't distinguish arrays from plain objects — for that, Array.isArray() is the right tool.
How I use this
Two habits fall straight out of the primitive/object split. First, when a value crosses a function boundary and I don't want surprises, I copy it ({ ...obj } for shallow, structuredClone for deep) rather than trusting the caller not to mutate. Second, type checks: typeof for primitives, Array.isArray() for arrays, instanceof for class instances, and === null / === undefined for those two specifically — never typeof. The NaN edge case I now handle with Number.isNaN(), which is correct, unlike the global isNaN() that coerces.
References
[1] Mozilla, "JavaScript data types and data structures," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
[2] I. Kantor, "Data types," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/types
[3] Mozilla, "Number — IEEE 754 double-precision," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
[4] Mozilla, "BigInt," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
[5] Altcademy, "What is null in JavaScript," 2023. [Online]. Available: https://www.altcademy.com/blog/what-is-null-in-javascript/
[6] JavaScript Tutorial, "Symbol data type in JavaScript," 2023. [Online]. Available: https://www.javascripttutorial.net/symbol/
[7] I. Kantor, "Objects," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/object
[8] Mozilla, "typeof operator," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
Knowledge check · Question 1 of 5
`typeof null` returns…
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!