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
Primitives vs objects
Section titled “Primitives vs objects”let count = 42; // primitive on stack (or inline in object slot)const user = { id: 1 }; // reference on stack → object on heapWhen you pass user to a function, you copy the reference, not the object. Both variables can point to the same heap object.
Why this matters for leaks
Section titled “Why this matters for leaks”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
Broken pattern
Section titled “Broken pattern”function processBatch(items) { const results = items.map((i) => ({ ...i, processed: true })); window.lastResults = results; // heap objects pinned globally}Fixed pattern
Section titled “Fixed pattern”function processBatch(items) { return items.map((i) => ({ ...i, processed: true })); // results die with the caller unless explicitly returned/stored}