← back to the section

In JavaScript, "variables don't have types — values do". A variable can hold a string today and a number tomorrow, and that's perfectly legal. That's why understanding the type system matters more here than in strict languages: it isn't checked by a compiler — it operates right at runtime, and it surprises those who don't know it.

Seven primitives and everything else

The primitive types: string, number, boolean, undefined, null, symbol, bigint. Everything else is an object: plain objects, arrays, functions, dates, regular expressions.

typeof "hi";        // "string"
typeof 42;          // "number"
typeof true;        // "boolean"
typeof undefined;   // "undefined"
typeof Symbol();    // "symbol"
typeof 10n;         // "bigint"

typeof {};          // "object"
typeof [];          // "object"  — an array is an object too!
typeof function(){} // "function" — a special answer for functions
typeof null;        // "object"  — the language's famous bug

Two of typeof's answers deserve a comment. typeof null === "object" is a historical mistake from the first implementation that can't be fixed without breaking the internet: check for null with the comparison x === null. And "function" is a courtesy: functions are formally objects, but typeof singles them out.

typeof can't tell an array from an object — that's what Array.isArray() is for:

Array.isArray([]);  // true
Array.isArray({});  // false

undefined and null: two kinds of "nothing"

The language has two "empty" values, and by convention they mean different things:

  • undefined — "there was no value yet": an undeclared property, a parameter with no argument, a function without return.
  • null — "the value is intentionally empty": it is assigned explicitly to denote a deliberate absence.
let user;
user;              // undefined — never assigned
const found = null; // null — we searched and deliberately found nothing

The practical consequence: if you see undefined in your data, most likely someone forgot to assign or mistyped a property name; null means someone put the emptiness there on purpose. Checking "neither one nor the other" in a single line: x == null — a rare case where loose comparison is deliberately useful.

number: one type for everything, plus NaN

JavaScript has no separate integers and floats — only number (a 64-bit float). Hence two classics:

0.1 + 0.2 === 0.3;        // false! 0.30000000000000004
Number.MAX_SAFE_INTEGER;  // 9007199254740991 — beyond this, integers "lie"

Fractional money is never computed in number "head-on" — it's computed in cents or via string-based libraries; for giant integers there's bigint.

NaN ("not a number") is the result of a meaningless numeric operation. Its treachery: NaN is the only value not equal to itself:

NaN === NaN;        // false
Number.isNaN(NaN);  // true — the correct check

If the comparison x === x yields false, you're looking at NaN. That's not a joke — it's a real technique from old code.

Value versus reference

The main behavioral fork: primitives are copied by value, objects — by reference.

let a = 1;
let b = a;
b = 2;
a; // 1 — the copy is independent

const x = { n: 1 };
const y = x;
y.n = 2;
x.n; // 2! — x and y point to the SAME object

This is the foundation on which both copying objects and React's state-update rules stand. For now, keep the rule itself in mind: assigning an object doesn't create a new object — only a second reference to the same one.

Boxing: why a string has methods

A string is a primitive, yet "hi".toUpperCase() works. How? When you access a property of a primitive, the engine momentarily wraps it in an object (String, Number, Boolean), calls the method, and throws the wrapper away. Hence the rule: the wrappers exist, but you should never create them by hand (new String("hi")) — you'll get an object with all the surprises of object behavior:

typeof new String("hi"); // "object", not "string"
new Boolean(false) ? 1 : 2; // 1! an object is always truthy

In short

  • Types belong to values, not variables. Seven primitives; everything else is an object (arrays and functions too).
  • typeof null === "object" is a language bug; check null with === null, arrays with Array.isArray().
  • undefined — "never assigned", null — "empty on purpose". x == null catches both.
  • One numeric type: 0.1 + 0.2 !== 0.3, money goes in cents. NaN isn't equal to itself; the check is Number.isNaN().
  • Primitives are copied by value, objects by reference. That's the cause of half the "mystical" bugs.
  • Primitive wrappers work automatically; new String() by hand — never.
  • Type coercion — what happens when types meet in one expression.
  • Objects: references, copying, and immutability — the continuation of the "by value vs by reference" theme.
  • JavaScript essentials for TypeScript — how static typing sits on top of this type system.