Skip to content

Stack vs heap

JavaScript engines split memory into two main regions:

  • Stores execution context: call frames, local primitives, references (pointers) to heap objects.
  • Fast allocation/deallocation — pops when a function returns.
  • Limited size (overflow → RangeError: Maximum call stack size exceeded).
  • Stores objects, arrays, functions, closures, and most non-primitive data.
  • Slower — managed by the garbage collector.
  • Can grow large (until OS / V8 limits).
flowchart TB
  subgraph stack [Stack]
    frame1["main()"]
    frame2["render()"]
    frame3["createUser()"]
    ref1["ref → User object"]
  end
  subgraph heap [Heap]
    user["User { name, orders[] }"]
    orders["Array of Order objects"]
  end
  ref1 --> user
  user --> orders
let count = 42; // primitive on stack (or inline in object slot)
const user = { id: 1 }; // reference on stack → object on heap

When you pass user to a function, you copy the reference, not the object. Both variables can point to the same heap object.

Leaks live on the heap. The stack cleans itself when functions return. A leak happens when a heap object stays reachable after you think you’re “done” with it — usually via:

  • A global variable
  • A closure
  • A DOM node reference
  • A timer or listener callback
function processBatch(items) {
const results = items.map((i) => ({ ...i, processed: true }));
window.lastResults = results; // heap objects pinned globally
}
function processBatch(items) {
return items.map((i) => ({ ...i, processed: true }));
// results die with the caller unless explicitly returned/stored
}