Every running program splits its working memory into two regions that behave nothing alike: the stack and the heap. Knowing which one your data lives in explains why some allocations are free, why others leak, and why a runaway recursion crashes differently than a runaway loop that keeps allocating.
The stack: your program's call history
The stack is a record of which functions are currently running. Each time you call a function, the runtime pushes a new stack frame onto the top. That frame holds the function's local variables, its parameters, and a return address — the spot in the caller to jump back to when this function finishes. When the function returns, its frame is popped off and the memory is reclaimed instantly. Nothing has to be hunted down or freed by hand.
This is why stack allocation is so cheap. The stack is strictly Last-In-First-Out, so "allocating" space for a frame is just moving a single pointer (the stack pointer) down by a known amount, and "freeing" it is moving the pointer back up. There's no bookkeeping, no search for a free block, no fragmentation. The cost is fixed and tiny — often a handful of CPU instructions.
The catch is lifetime. A stack frame disappears the moment its function returns, so anything stored in it is gone too. That makes the stack perfect for short-lived, fixed-size values that don't need to survive the call — counters, loop indices, small structs, a function's scratch variables.
The heap: data that outlives the call
When data needs to live longer than the function that created it, or when its size isn't known until runtime, it goes on the heap. The heap is a large pool of memory you can carve pieces out of on demand: ask the allocator for some bytes, and it finds a free block and hands you back a pointer (or a reference) to it. That pointer can be stored on the stack, returned to a caller, or passed around — the underlying data stays put regardless of which functions come and go.
This flexibility costs more. Allocating means the allocator has to find a suitable free block, which is real work compared to bumping a pointer. Over time, as blocks of different sizes are allocated and freed, the heap can develop fragmentation — enough total free memory exists, but it's scattered in pieces too small to satisfy a large request.
Heap memory also has to be reclaimed deliberately. How that happens is the biggest difference between languages:
-
Manual (C, C++): you call
malloc/freeornew/deleteyourself. Forget to free and you leak; free twice or use after freeing and you get undefined behavior. - Garbage-collected (Java, Go, Python, JavaScript, C#): a runtime tracks which heap objects are still reachable and frees the rest automatically. Safer, at the cost of some runtime overhead and less predictable timing.
In Java and Python, the variable name and reference live on the stack, but almost every object you create lives on the heap —
newand object literals allocate there by default. In C/C++ you choose: a local declared asPoint p;sits in the stack frame, whilePoint* p = new Point();puts the object on the heap. The deciding factor is always how long the data must live, not how it looks in the source.
A concrete contrast
Consider a function that returns a value versus one that returns an object that must persist. In C-flavored pseudocode:
int sum(int a, int b) {
int result = a + b; // 'result' lives in this frame, on the stack
return result; // its value is copied out; the frame is then gone
}
Node* make_node(int value) {
Node* n = malloc(sizeof(Node)); // the Node lives on the heap
n->value = value;
return n; // the pointer is returned; the Node survives
}
The int is automatic: it appears when sum starts and vanishes when it returns, and that's fine because only its value escapes. The Node must outlive make_node, so returning a pointer to a stack frame would be a bug (the frame is destroyed on return). It goes on the heap, and someone — you in C, the GC in Java — is responsible for releasing it later.
When each region runs out
Both regions have limits, and they fail in distinct ways:
- Stack overflow happens when the stack grows past its fixed size — classically from deep or infinite recursion, where each call pushes another frame and none of them return. The stack has a hard cap (often around a megabyte per thread, though it varies by platform and configuration), so it fills fast.
- Heap exhaustion happens when you keep allocating heap memory without releasing it — a memory leak, an unbounded cache, loading something far too large. The program slows, then the allocator fails or the OS kills it (the familiar "out of memory").
A quick mental model: the stack overflows from too many calls; the heap exhausts from too much data.
Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.
Top comments (0)