The Stack and the Heap

2.Trays and a Warehouse

A

In this chapter

We'll learn the two areas where a running program keeps its data — the stack (a tidy pile of trays, cleaned up automatically) and the heap (a big warehouse that keeps things until they're cleared) — plus the call stack, local variables and dynamic allocation.

14–16 min

The Problem in Real Life

John opens the seat-hold service code and points at one function, holdSeat. It runs every time a fan picks a seat — on a busy day, about 300,000 times.

"Every time this runs, it creates some data," he says. "Some of that data disappears the moment the function finishes. Some of it stays behind. We need to know which is which."

A

So some memory cleans itself up, and some doesn't? How do I tell the difference?

Anna

One Pile of Memory vs. Two Very Different Areas

Two areas, two rules

Short-lived data and long-lived data are stored in different places with different rules.

Automatic cleanup — or not

One area cleans itself up when a function ends. The other keeps things until they're no longer needed.

The leak lives in one of them

Knowing where data lives tells you where a leak can hide.

The Stack and the Heap

A running program splits its memory into two main working areas. Each has a picture that makes it easy to remember.

  • The stack — a pile of trays in a cafeteria: each time a function is called, a fresh tray is put on top of the pile. On that tray go the function's local variables (Act 07) and a note saying where to go back to when it's done. When the function finishes, its tray is taken off the top and everything on it is gone — instantly and automatically. The stack is small, very fast and perfectly tidy. It's the same last-in, first-out idea as the stack of plates in Act 09.
  • The call stack — the trays in action: when holdSeat calls checkSeat, a tray for checkSeat goes on top of the tray for holdSeat. When checkSeat returns, its tray comes off, and holdSeat continues where it left off. The pile of trays at any moment is called the call stack, and each tray is a stack frame. When an error happens, the "stack trace" you see (Act 17) is simply a list of the trays that were on the pile at that moment. And if functions keep calling functions forever, like recursion without a base case in Act 09, the pile reaches the ceiling: stack overflow.
  • The heap — a big warehouse: some data has to outlive the function that created it, or is too big or too unpredictable for a tray: a list of seats, an object describing a hold, a timer. That goes into the heap — a large, flexible storage area. Things in the heap stay there until they're cleaned up (next chapter). The heap is bigger and more flexible than the stack, but a little slower, and it doesn't clean itself up when a function ends.
  • How the two work together: when holdSeat creates a hold object, the object itself goes into the heap warehouse. The local variable on the stack tray holds only a reference to it — a note saying "the hold is on shelf 4,021." When the function ends, the tray and its note are gone. Whether the hold object in the warehouse can also be thrown away depends on one thing: does anything else still have a note pointing to shelf 4,021?
Table — Stack vs. heap
FeatureStack (pile of trays)Heap (warehouse)
What goes thereLocal variables and call informationObjects, arrays, anything long-lived or sized at run time
SizeSmallLarge
SpeedVery fastA bit slower
CleanupAutomatic when the function returnsOnly when nothing references it (garbage collection) or manually
Typical problemStack overflow (too many trays)Memory leak (things never cleared)
Table — The call stack as holdSeat runs
MomentTrays on the pile (top first)
holdSeat startsholdSeat
holdSeat calls checkSeatcheckSeat, holdSeat
checkSeat calls findSeatfindSeat, checkSeat, holdSeat
findSeat returnscheckSeat, holdSeat
checkSeat returnsholdSeat
holdSeat returns(empty)

One call to holdSeat

Stack: tray for holdSeat

seatId, now, a reference → hold

the references point to

Heap: hold object

{ seatId, fanId, heldUntil }

Heap: 10-minute timer

will release the seat

when holdSeat ends

Function returns

the tray is removed instantly

Heap objects stay

as long as anything still references them — like the timers map

Where each piece of holdSeat lives
const timers = new Map(); // created once at startup — lives in the heap all day
function holdSeat(seatId, fanId) {
const now = Date.now(); // stack: local value, gone when the function ends
const hold = { // heap: a new object
seatId,
fanId,
heldUntil: now + HOLD_DURATION_MS,
};
const timer = setTimeout(() => releaseSeat(seatId), HOLD_DURATION_MS); // heap: a timer
timers.set(seatId, timer); // the long-lived map now references the timer
return hold;
}

When holdSeat returns, now, hold and timer (the names) disappear from the stack. The timer object itself stays, because the timers map still points to it.

Dynamic allocation — asking for space while running: often a program can't know in advance how much memory it needs. How many seats will this event have — 200 or 20,000? How many holds today? So it asks for memory while it runs, exactly when it needs it. That's called dynamic allocation, and the space comes from the heap. Object allocation is the everyday case: every time JavaScript runs { ... }, [ ... ] or new Something(), it allocates a new object in the heap.

In the seat-hold service: Anna traces one call of holdSeat. A tray goes on the stack with local variables seatId and now. A new hold object { seatId, fanId, heldUntil } is allocated in the heap. A timer is created to release the seat after 10 minutes — also in the heap. Then the function returns; the tray disappears. But two things are still in the warehouse: the hold object and the timer. And on line 31, she spots it: timers.set(seatId, timer). A long-lived map, created when the service starts, is storing a note to every timer. Forever?

Key Takeaway

The stack is a pile of cafeteria trays: each function call gets a tray for its local variables, removed automatically when the function returns — small, fast and tidy. The heap is a big warehouse for data that must outlive a function or whose size isn't known in advance; it doesn't clean itself up when functions end. Stack variables usually hold references to objects that live in the heap.

Why This Matters

Almost every memory problem is about the heap: something put in the warehouse and never cleared. Knowing that local variables vanish but heap objects stay as long as they're referenced lets you look at any code and ask the right question: "what long-lived thing is still holding a note to this?" Stack overflows, stack traces and recursion all make sense once you picture the pile of trays.

Anna has a suspect: a map that keeps a note to every timer. But JavaScript is supposed to clean up memory by itself — that's what everyone says. So why isn't it cleaning these up? To answer that, she needs to know how memory gets cleaned at all.

Next