DEV Community

Abhishek Mishra
Abhishek Mishra

Posted on

Understanding Stack vs. Heap in JavaScript: Primitives and Non-Primitives

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

  1. Primitives on the Stack
  2. Objects and Arrays on the Heap
  3. Stack vs. Heap: Key Differences
  4. Why It Matters: Copying and Mutation
  5. Code Examples
  6. 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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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!

Image description

Top comments (0)