---
title: "03 — Data Types — Primitives vs Objects, and the typeof Quirks"
uid: datatypes
tags: ["datatypes", "roadmap:javascript", "symbol", "typeof", "objects", "bigint", "javascript", "primitives"]
excerpt: "There are exactly two categories of value in JavaScript — primitives (copied by value) and objects (copied by reference) — and seven primitive types feed them."
date: 2026-08-13T03:28:07+0000
source: https://www.aveshina.my.id/en/blog/datatypes
---

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

```figure
<svg viewBox="0 0 740 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Two categories of JavaScript values. Left column: Primitives, seven tokens (Number, String, Boolean, Null, Undefined, Symbol, BigInt), each labelled copied by value, immutable. Right column: Objects, one heap box labelled copied by reference, mutable, with subtypes Array, Function, Object, Map noted.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- Primitives column -->
    <rect x="30" y="30" width="300" height="240" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="180" y="55" font-size="14" font-weight="700" fill="#1e1b4b" text-anchor="middle">Primitives — by value</text>
    <g font-size="11" font-family="ui-monospace, monospace" fill="#1e1b4b">
      <text x="50" y="85">Number</text>
      <text x="50" y="110">String</text>
      <text x="50" y="135">Boolean</text>
      <text x="50" y="160">Null</text>
      <text x="50" y="185">Undefined</text>
      <text x="50" y="210">Symbol</text>
      <text x="50" y="235">BigInt</text>
    </g>
    <text x="180" y="258" font-size="10" font-style="italic" fill="#475569" text-anchor="middle">immutable · copied by value</text>

    <!-- Objects column -->
    <rect x="410" y="30" width="300" height="240" rx="10" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="560" y="55" font-size="14" font-weight="700" fill="#052e16" text-anchor="middle">Objects — by reference</text>
    <g font-size="11" font-family="ui-monospace, monospace" fill="#052e16">
      <text x="430" y="100">Object</text>
      <text x="430" y="125">Array</text>
      <text x="430" y="150">Function</text>
      <text x="430" y="175">Date, RegExp</text>
      <text x="430" y="200">Map, Set</text>
      <text x="430" y="225">Error</text>
    </g>
    <text x="560" y="258" font-size="10" font-style="italic" fill="#475569" text-anchor="middle">mutable · copied by reference</text>
  </g>
</svg>
```

## 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:

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](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](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](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](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/](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/](https://www.javascripttutorial.net/symbol/)

[7] I. Kantor, "Objects," The Modern JavaScript Tutorial, 2024. [Online]. Available: [https://javascript.info/object](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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof)

```quiz
Q: `typeof null` returns…
- "null"
- "object"
correct: 1
explain: A long-standing bug in the original implementation. typeof null returns "object", which is why null is checked with === null, never with typeof.

Q: Why does 0.1 + 0.2 !== 0.3 in JavaScript?
- Numbers are IEEE 754 double-precision floats, so most decimals can't be represented exactly
- It's a parser bug fixed in ES2020
correct: 0
explain: JavaScript has one numeric type (Number) using IEEE 754. 0.1 and 0.2 are not exactly representable, so the sum is 0.30000000000000004. Use a small epsilon or BigInt (for integers) when exactness matters.

Q: Assigning `const b = a` where `a` is an object, then mutating `b.x`, also changes `a.x`. Why?
- Objects are copied by reference, so b and a point to the same object
- const creates a shared binding between two names
correct: 0
explain: Objects are reference types. The assignment copies the reference, not the value. Both names point to one object, so mutating through either is visible through the other. Primitives don't behave this way.

Q: How do you correctly distinguish an array from a plain object?
- typeof arr === "array"
- Array.isArray(arr)
correct: 1
explain: typeof returns "object" for both arrays and plain objects. Array.isArray() is the reliable check.

Q: `null` vs `undefined` — which signals intentional absence?
- null signals intentional absence; undefined means nothing was ever set
- undefined signals intentional absence; null means nothing was ever set
correct: 0
explain: null is the deliberate "no value here." undefined is the default for declared-but-unassigned variables and functions that return nothing.
```
