Fundamentalsbeginner

Primitive vs Reference Types

The distinction between value types (numbers, strings, booleans) and reference types (objects, arrays, functions), and how it affects copying and comparison.

JavaScript has two broad categories of data: primitives and reference types, and how each is stored and copied has real consequences for how you write and debug code. This is a foundational concept that underlies closures, function arguments, equality checks, and immutability patterns, so interviewers use it to gauge whether your mental model of the language is solid.

A primitive is like handing someone a photocopy of a document — they can scribble on their copy without touching yours. A reference type is like handing someone the key to your apartment — anything they do inside changes the same apartment you both have access to.

Key Concepts

1
Primitives — string, number, boolean, null, undefined, symbol, and bigint — are immutable and compared by value. When you assign a primitive to a new variable or pass it to a function, JavaScript copies the actual value, so the two variables are completely independent afterward. Reference types — objects, arrays, and functions — are stored on the heap, and variables referencing them actually hold a pointer to that memory location. Assigning or passing a reference type copies the pointer, not the underlying data, so two variables can end up pointing at the same object.
stringnumberbooleannullundefined
2
This explains why mutating an object passed into a function affects the caller's copy too — both variables point at the same memory — while reassigning a primitive parameter inside a function never leaks out. It also explains why {} === {} is false: they are two different objects in memory even though their contents are identical, whereas 1 === 1 is true because primitives compare by value.
{} === {}false1 === 1true
3
Understanding this distinction is essential for writing bug-free code around copying: a shallow copy via spread ({...obj}) or Object.assign() only copies one level deep, so nested objects are still shared references. For true independence you need a deep clone, via structuredClone(), a recursive copy, or a library.
{...obj}Object.assign()structuredClone()