Primitives and objects in memory
JavaScript keeps data in two places in memory: the stack holds primitives, while the heap holds objects and the stack only holds a reference to them. That is exactly why a primitive is copied by value and an object is copied by reference.
Theory
TL;DR
- The stack is fast memory for primitives and for references to objects.
- The heap is flexible memory where the objects themselves live: objects, arrays, functions,
Date,Map,Set. - A primitive is copied by value: the new variable owns its own copy of the data.
- An object is copied by reference: both variables look at the same region of the heap.
- Spread breaks the reference only at the top level; nested structures need a deep copy (
structuredClone()).
Quick example
// primitive: the value is copied
let a = 10;
let b = a;
b = 20;
console.log(a, b); // 10 20
// object: the reference is copied
const user1 = { name: 'Tim' };
const user2 = user1;
user2.name = 'Alex';
console.log(user1.name, user2.name); // "Alex" "Alex"Primitives: the stack and copying by value
Primitives are string, number, boolean, null, undefined, symbol, bigint. They are stored on the stack, a fast region of memory that holds the values themselves rather than references.
let a = 10;
let b = a; // the value 10 is copied
b = 20;
console.log(a); // 10
console.log(b); // 20In memory (stack):
+---------------+
| a = 10 |
| b = 20 | <- a separate copy
+---------------+Every variable stores its own value, so a change in one does not affect the other.
Objects: the heap and copying by reference
Objects, and with them arrays, functions, dates and other complex structures, are stored on the heap, the memory area for "flexible" structures. The stack holds only a reference (a pointer) to that object.
const user1 = { name: 'Tim' };
const user2 = user1; // the reference (the pointer) is copied
user2.name = 'Alex';
console.log(user1.name); // "Alex"In memory:
STACK HEAP
+---------------+ +----------------------+
| user1 --+ | | { name: "Alex" } |
| user2 --+---------> +----------------------+
+---------------+Both variables point at one and the same object, so a change made through user2 shows up in user1.
A shallow copy and why it does not save nested objects
Dedicated copying techniques create a new reference:
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // the spread operator
arr2.push(4);
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3, 4]In memory:
arr1 --> [1, 2, 3]
arr2 --> [1, 2, 3, 4]Different references, so the changes do not touch the original.
But when the object is nested, spread makes only a shallow copy:
const user1 = { name: 'Tim', address: { city: 'Kyiv' } };
const user2 = { ...user1 };
user2.address.city = 'London';
console.log(user1.address.city); // "London"Why that happens:
user1.address --+
user2.address --+--> they point at the same inner objectIn other words, the inner objects are still copied by reference.
Deep copying
To create a fully independent copy you have to break every reference inside.
The old way:
const user1 = { name: 'Tim', address: { city: 'Kyiv' } };
const user2 = JSON.parse(JSON.stringify(user1));
user2.address.city = 'Berlin';
console.log(user1.address.city); // "Kyiv"The modern way (Node.js 17+ and browsers from roughly 2022):
const user2 = structuredClone(user1);structuredClone() produces a deep copy and leaves no shared references at all.
Stack vs heap and the summary table
STACK (fast memory)
------------------------
a = 5
b = "Hello"
user -> (a reference to an object)
HEAP (flexible memory)
------------------------
{ name: "Tim", age: 25 }The stack stores simple values and references, while the heap stores the complex objects themselves.
An everyday analogy:
- A primitive is like a sheet of paper with a number on it: make a copy and you get another sheet, and changing the number on it leaves the original intact.
- An object is like a folder of documents: if you hand a colleague a link to the same folder, you both see the same files.
| Data type | Where it is stored | How it is copied | What is passed |
|---|---|---|---|
Primitives (string, number, boolean, null, undefined, symbol, bigint) | Stack | By value | The value itself |
Objects (object, array, function, Date, Map, Set) | Heap | By reference | A pointer to the object |
In short:
- Primitives are copied by value, a new copy is created.
- Objects are copied by reference, both variables "look at" the same region of memory.
- For an independent copy of an object use
structuredClone(),JSON.parse(JSON.stringify(obj))orlodash.cloneDeep().
Common mistakes
- Assuming
[...arr]or{ ...obj }gives a full copy. They copy only the top level. - Believing
constmakes an object immutable.constpins the reference, not the contents of the heap. - Applying
JSON.parse(JSON.stringify(obj))to anything at all: it dropsundefined, functions andSymbol, turns aDateinto a string and throws on circular references. - Forgetting that strings in JavaScript are immutable:
str[0] = 'X'changes nothing, because a primitive cannot be edited in place. - Expecting a large object to be freed the moment you assign
nullto one of the variables. While at least one live reference remains, the garbage collector will not reclaim it.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.