Data Types
Every value in JavaScript has a type, and that type decides what you can do with the value — how it compares, how it converts, and whether it can be changed in place. JavaScript defines exactly eight types: seven primitives and the catch-all object type. Knowing them cold is the foundation for understanding equality, type coercion, and the difference between values copied by content and references shared by identity.
The eight types
JavaScript groups values into seven primitive types plus object. Primitives are simple, immutable values; objects are collections of properties. Here is the full set, with what typeof reports for each.
| Type | Example | typeof result |
|---|---|---|
| string | "hello" | "string" |
| number | 42, 3.14, NaN | "number" |
| boolean | true, false | "boolean" |
| null | null | "object" |
| undefined | undefined | "undefined" |
| bigint | 9007199254740993n | "bigint" |
| symbol | Symbol("id") | "symbol" |
| object | {}, [], () => {} | "object" (or "function") |
Gotcha:
typeof null === "object"is a famous, decades-old bug in the language that can never be fixed without breaking the web. To test fornull, compare directly:value === null.
Primitives in detail
A string is an immutable sequence of UTF-16 code units. Use double quotes, single quotes, or backticks; backticks add interpolation and multi-line support.
const name = "Ada";
const greeting = `Hello, ${name}!`;
console.log(greeting);
Output:
Hello, Ada!
A number is a 64-bit IEEE-754 double — there is no separate integer type. It covers integers, floats, and three special values: Infinity, -Infinity, and NaN (the result of an invalid math operation).
console.log(0.1 + 0.2); // floating-point rounding
console.log(1 / 0); // Infinity
console.log(Number("abc")); // NaN
Output:
0.30000000000000004
Infinity
NaN
A boolean is simply true or false, the result of comparisons and the input to conditionals.
null and undefined both represent “no value”, but with different intent. undefined means a value was never assigned — an uninitialized variable, a missing argument, or an absent property. null is an intentional, programmer-assigned “empty” value.
let notSet; // undefined by default
const cleared = null; // explicitly empty
console.log(notSet, cleared);
Output:
undefined null
A bigint represents integers of arbitrary size, beyond the safe range of number (2^53 - 1). Create one with an n suffix or the BigInt() function. You cannot mix bigint and number directly in arithmetic.
const big = 9007199254740993n;
console.log(big + 1n);
Output:
9007199254740994n
A symbol is a guaranteed-unique, immutable identifier. Two symbols are never equal, even with the same description, which makes them ideal for collision-free object keys.
const id = Symbol("id");
const other = Symbol("id");
console.log(id === other);
Output:
false
Objects
Everything that is not a primitive is an object: plain objects, arrays, functions, dates, maps, and more. Objects are keyed collections of properties and are the only mutable type. Note that typeof reports "function" for callable objects and "object" for arrays — use Array.isArray() to detect arrays specifically.
console.log(typeof {}); // object
console.log(typeof []); // object
console.log(typeof (() => {})); // function
console.log(Array.isArray([])); // true
Output:
object
object
function
true
Dynamic typing
JavaScript is dynamically typed: types belong to values, not to variables. A single binding can hold a string now and a number later — the engine never complains. This flexibility is convenient but means type errors surface at runtime rather than compile time, which is exactly the problem TypeScript exists to solve.
let value = "text"; // string
console.log(typeof value);
value = 100; // now a number
console.log(typeof value);
value = true; // now a boolean
console.log(typeof value);
Output:
string
number
boolean
Primitives are copied, objects are shared
This is the single most consequential difference between the two categories. Primitives are assigned and passed by value — you get an independent copy. Objects are assigned and passed by reference — the variable holds a pointer, so two variables can point at the same underlying object.
let a = 10;
let b = a; // copy of the value
b = 20;
console.log(a); // 10 — unaffected
const arr1 = [1, 2];
const arr2 = arr1; // copy of the reference
arr2.push(3);
console.log(arr1); // [1, 2, 3] — same array!
Output:
10
[ 1, 2, 3 ]
This behavior drives surprising bugs around mutation, function arguments, and equality. It has its own dedicated page — see Primitives vs references for the full mental model.
Best Practices
- Use
typeoffor primitives, but testnullwithvalue === nullbecause of the historicaltypeof null === "object"quirk. - Detect arrays with
Array.isArray(), nottypeof, since arrays report"object". - Prefer
undefinedfor “not yet set” and reservenullfor an intentional empty value, then stay consistent. - Never check for
NaNwith===(it is never equal to itself) — useNumber.isNaN(). - Reach for
bigintonly when you genuinely exceedNumber.MAX_SAFE_INTEGER, and avoid mixing it withnumber. - Remember that primitives copy by value while objects share by reference — it determines whether a change is local or global.