AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 03 — Data Types — Primitives vs Objects, and the typeof Quirks

03 — Data Types — Primitives vs Objects, and the typeof Quirks

August 13, 20265 min read
Download as Markdown

"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.

Primitives — by value Number String Boolean Null Undefined Symbol BigInt immutable · copied by value Objects — by reference Object Array Function Date, RegExp Map, Set Error mutable · copied by reference

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 change

Two 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!