Difference between primitives and non-primitives in JavaScript
Primitives are immutable values stored directly in memory by value; non-primitives (objects) are mutable structures accessed through a reference to heap memory.
Theory
TL;DR
- A primitive is like a printed postcard: copying it gives you a fully independent duplicate, and changing yours doesn't touch the original
- An object is like a shared Google Doc link: everyone holding that link edits the same document
- Assignment with a primitive copies the value; assignment with an object copies the reference (a pointer)
- Use primitives for simple, stable data (age, ID, flag); use objects for grouped or changing data (cart, config, user profile)
===on primitives compares values;===on objects checks whether they're the same reference in memory
Quick example
// Primitives: assignment copies the value
let a = 5;
let b = a; // b gets its own copy of 5
a = 10;
console.log(a); // 10
console.log(b); // 5 — not affected
// Objects: assignment copies the reference
let x = { count: 5 };
let y = x; // y points to the same object as x
x.count = 10;
console.log(x.count); // 10
console.log(y.count); // 10 — same object, same resultBoth x and y point to one object in memory. There is no second object. So mutating through x is visible through y.
Key difference
When you write let b = a with a primitive, JavaScript creates a new memory slot and copies the actual value into it. When you write let y = x with an object, JavaScript copies only the address of the object (a 64-bit pointer in V8). Both variables point to the same heap location. Any mutation through one variable shows up in the other.
When to use
- Age, price, toggle flag, user ID -> primitive
- Shopping cart, user profile, app config -> object
- Unique key that should not accidentally equal anything else ->
Symbol(primitive) - Grouped data with methods -> object
Comparison table
| Aspect | Primitives | Non-primitives (Objects) |
|---|---|---|
| Storage | Stack (direct value) | Heap (data) + stack (reference) |
| Assignment | Copies value | Copies reference |
| Mutability | Immutable | Mutable |
=== comparison | Checks value | Checks reference |
| Size | Fixed, small | Dynamic |
| Types | string, number, boolean, null, undefined, bigint, symbol | {}, [], () => {} |
| When to use | Constants, IDs, calculations | Data structures, configs, state |
How it works internally
V8 (Chrome and Node.js) allocates primitives on the stack for fast access with no garbage collection needed. Objects go on the heap; the stack holds only a 64-bit pointer to that heap location. When you do obj.prop = 1, V8 follows the pointer and mutates the shared heap data. When no references point to an object anymore, the garbage collector frees it.
One detail worth knowing: V8 interns short identical strings, meaning they can share a single memory slot. That is a performance optimization you do not control, but it is why identical string primitives are always === equal.
Common mistakes
Mistake 1: assuming arrays copy on assignment
let arr1 = [1, 2];
let arr2 = arr1; // copies reference, not elements
arr2.push(3);
console.log(arr1); // [1, 2, 3] — surprise
// Fix: use spread
let arr2 = [...arr1];Mistake 2: trying to mutate a string
let str = 'hello';
str[0] = 'H';
console.log(str); // 'hello' — strings are immutable, assignment has no effect
// Fix: build a new string
str = 'H' + str.slice(1); // 'Hello'Mistake 3: comparing objects with ===
console.log({ a: 1 } === { a: 1 }); // false — two different references
// Fix: compare by content
JSON.stringify(obj1) === JSON.stringify(obj2);
// For nested structures: lodash isEqualMistake 4: forgetting null is a primitive
let obj = null;
obj.prop = 'test'; // TypeError: Cannot set properties of null
// Fix: guard before accessing
if (obj) obj.prop = 'test';Real-world usage
- React: state is an object (
useState({ name: 'Alice' })); update it by creating a new object ({ ...user, name: 'Bob' }) because mutating directly bypasses React's change detection - Express:
req.bodyis a mutable object;res.status(200)takes a primitive number - Node.js:
process.envis an object shared across the app;parseInt(process.env.PORT)gives a primitive you can pass around safely - Lodash
cloneDeep: when you need a fully independent copy of a nested object, not just a pointer copy
In practice, the object reference model causes bugs more often than the value model. Most "why did my state change unexpectedly?" questions in React trace back to shared references.
Follow-up questions
Q: What happens when you do let x = 5; x = { value: 5 };?
A: x changes from holding a primitive number to holding a reference to a new object. JavaScript does not enforce types on variables, so the reassignment just works. The number 5 is gone.
Q: Why does typeof null return 'object'?
A: It is a bug from 1995 that was never fixed to preserve backward compatibility. null is a primitive. The typeof result is wrong, but changing it would break too much existing code.
Q: Can primitives have methods like .toUpperCase() or .toString()?
A: Yes, through auto-boxing. When you call 'hello'.toUpperCase(), JavaScript temporarily wraps the string primitive in a String object, calls the method, then discards the wrapper. The original primitive stays unchanged.
Q: If you pass an array into a function and push an element inside, does the original array change?
A: Yes. Arrays are objects, so passing one passes the reference. Any mutation inside the function affects the original. To avoid this, pass a copy: fn([...arr]).
Q: (Senior) Two objects with identical properties give === false. What are the ways to check structural equality?
A: JSON.stringify(a) === JSON.stringify(b) works for flat objects with consistent key order. For nested structures or objects with Date or RegExp, use lodash isEqual, which handles edge cases correctly.
Examples
Primitive: value copy inside a function
function increment(n) {
n += 1;
return n;
}
let count = 5;
let result = increment(count);
console.log(count); // 5 — original unchanged
console.log(result); // 6The function receives a copy of 5. Changing n inside does nothing to count outside. This is the value model in action.
Object reference: React state mutation bug
// BAD: mutates the existing state object
const [user, setUser] = useState({ name: 'Alice', prefs: { theme: 'dark' } });
user.prefs.theme = 'light'; // direct mutation
setUser(user); // React sees the same reference — no re-render
// GOOD: create a new object at every level that changed
setUser({
...user,
prefs: { ...user.prefs, theme: 'light' }
});
// React sees a new reference, triggers re-render correctlysetUser(user) passes the same pointer. React's shallow compare sees an identical reference and skips the re-render. The fix is always to return a new object, because React relies on reference inequality to detect changes.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.