JavaScript manages memory in two main areas—the stack and the heap—and knowing how data fits into each can help you avoid confusing bugs. In this post, we’ll cover the difference between primitive and non-primitive types, explain where they live in memory, and show practical examples you can use right away.
Table of Contents
- Primitives on the Stack
- Objects and Arrays on the Heap
- Stack vs. Heap: Key Differences
- Why It Matters: Copying and Mutation
- Code Examples
- Conclusion
1. Primitives on the Stack
JavaScript’s primitive types include:
- String
- Number
- Boolean
- Null
- Undefined
- Symbol
- BigInt
When you declare a primitive, its value is stored directly on the call stack, a region of memory designed for fast, fixed-size allocations. Assigning one primitive variable to another creates a copy of the value:
let x = 42;
let y = x; // y receives its own copy of 42
y = 100;
console.log(x); // 42
Because each primitive has its own stack slot, changing y
does not affect x
.
2. Objects and Arrays on the Heap
Non-primitive types (objects, arrays, functions, and other complex structures) are allocated in the heap, a larger, dynamic region of memory suited for variable-sized data. Variables that hold non-primitives store a reference (pointer) to the heap location:
let objA = { name: 'Alice' };
let objB = objA; // both variables reference the same object
objB.name = 'Bob';
console.log(objA.name); // 'Bob'
Here, objA
and objB
point to the same heap object, so mutations via one reference are visible through the other.
3. Stack vs. Heap: Key Differences
Aspect | Stack | Heap |
---|---|---|
Storage for | Primitives & references | Actual object and array data |
Access speed | Very fast | Slower (managed by the JS engine) |
Size | Limited, fixed | Larger, dynamic |
Allocation order | Last-in, first-out (LIFO) | Managed by garbage collector |
Copy behavior | By value | By reference |
4. Why It Matters: Copying and Mutation
- Primitives are passed by value. You get independent copies, so you can’t accidentally overwrite another variable’s data.
- Non-primitives are passed by reference. Multiple variables can point to the same data, so changing one will affect all references.
Understanding these rules helps you decide when to clone data to avoid unintended side effects:
// Cloning an object to avoid shared references
let original = { count: 1 };
let clone = { ...original }; // shallow copy
clone.count = 2;
console.log(original.count, clone.count); // 1, 2
5. Code Examples
// Primitive example: separate copies
let a = 'hello';
let b = a;
b = 'world';
console.log(a, b); // "hello", "world"
// Reference example: shared object
let arr1 = [1, 2, 3];
let arr2 = arr1;
arr2.push(4);
console.log(arr1, arr2); // [1,2,3,4], [1,2,3,4]
// Creating a true copy of an array
let arr3 = arr1.slice(); // or [...arr1]
arr3.push(5);
console.log(arr1, arr3); // [1,2,3,4], [1,2,3,4,5]
6. Conclusion
Knowing where JavaScript stores your data—the stack for primitives and references, and the heap for objects and arrays—gives you clarity on assignment, copying, and mutation behavior. Keeping these distinctions in mind will help you write more predictable, bug-free code. Happy coding!
Top comments (0)