The short answer
Quick answer: The stack and the heap are two regions of a program's memory with different rules. The stack holds data for function calls: parameters, local variables and return addresses. It grows and shrinks automatically as functions are called and return, which makes it very fast but limited in size and lifetime. The heap holds data that must outlive a single function call or whose size is not known in advance. Heap memory is requested explicitly and must later be released, either by you or by a garbage collector.
| Stack | Heap | |
|---|---|---|
| Managed by | The compiler, automatically | The programmer or a garbage collector |
| Allocation cost | Almost nothing (move one pointer) | Higher (find a suitable free block) |
| Lifetime | Until the function returns | Until freed or unreachable |
| Size | Small, fixed limit (often around 1 to 8 MB per thread) | Limited by available memory |
| Size known at compile time? | Usually required | Not required |
| Layout | Contiguous, cache-friendly | Scattered |
| Typical bugs | Stack overflow | Leaks, dangling pointers, fragmentation |
How the stack works
Every thread has its own stack. Each time a function is called, a stack frame is pushed on top containing:
- The function's arguments.
- Its local variables.
- The return address: where to continue when the function finishes.
When the function returns, its frame is popped and the memory is instantly available for the next call.
int square(int n) {
int result = n * n; // lives in square's frame
return result;
}
int main(void) {
int x = 4; // lives in main's frame
int y = square(x);
return 0;
}
While square runs, the stack holds two frames: main's underneath and square's on top. When square returns, its frame disappears, taking n and result with it.
Allocation is nothing more than adjusting a single CPU register, the stack pointer. That is why the stack is so fast. It is also why it is last-in, first-out: you can only remove the most recent frame. The Wikipedia article on the call stack covers the layout in detail.
How the heap works
Sometimes stack rules do not fit:
- The data must outlive the function that created it.
- The size is only known at run time, such as a list that grows.
- The data is simply too big for the stack.
For these cases, a program asks an allocator for a block of heap memory: malloc in C, new in C++ and Java, or implicitly whenever you create an object or list in Python or JavaScript. The allocator finds a free block of the right size and returns its address, a pointer or reference.
int *make_scores(int count) {
int *scores = malloc(count * sizeof(int)); // heap
return scores; // still valid after return
}
The pointer scores itself lives on the stack and vanishes when the function returns. The block it points to lives on the heap and stays until someone calls free.
Heap allocation is slower because the allocator has to search for space, keep track of what is used, and cope with fragmentation: free memory broken into pieces too small to be useful.
Who frees heap memory?
Languages take three approaches:
| Approach | Languages | How it works |
|---|---|---|
| Manual | C, C++ (raw pointers) | You call free or delete yourself |
| Garbage collection | Java, Go, C#, JavaScript, Python | The runtime frees objects nothing can reach |
| Ownership | Rust, modern C++ smart pointers | The compiler frees a value when its owner goes out of scope |
See how garbage collection works for the second approach. The Rust book's chapter on ownership has one of the clearest explanations of the stack and heap you will find.
Value types and reference types
In many languages, a variable's type decides where its data lives.
- Value types (numbers, booleans, small structs) are stored directly in the variable. Copying the variable copies the data.
- Reference types (objects, arrays, strings in many languages) are stored on the heap. The variable holds only a reference. Copying the variable copies the reference, so both names refer to the same object.
let a = 5;
let b = a; // b gets its own copy
b = 10; // a is still 5
let p = { x: 5 };
let q = p; // q refers to the same object
q.x = 10; // p.x is now 10 too
This explains a lot of surprising behaviour in everyday code, and it is why immutability is such a useful discipline.
Compilers also optimise behind the scenes. Go and Java use escape analysis: if an object provably never leaves the function that created it, it can be placed on the stack and skip the heap entirely.
Common bugs
- Stack overflow. Too many nested calls, usually runaway recursion, exhaust the stack. See why recursion can crash your program.
- Returning a pointer to a local variable. The frame is gone after return, so the pointer refers to memory that will be reused.
- Memory leak. Heap memory is never freed, and usage keeps growing.
- Use after free / dangling pointer. Memory is freed while a pointer to it is still in use.
- Buffer overflow. Writing past the end of an array. On the stack this can overwrite the return address, a classic security vulnerability.
Why this matters for performance
Stack data is contiguous and constantly reused, so it is almost always in the CPU cache. Heap objects can be anywhere, and following a reference to one may be a cache miss. Code that creates many small heap objects is slower twice over: once for the allocations and again for the scattered memory access.
Practical habits:
- Prefer local variables and value types for small, short-lived data.
- Avoid allocating inside hot loops; reuse buffers.
- Use contiguous collections such as arrays and vectors over linked structures.
Where they sit in memory
Both regions are parts of a process's virtual address space. Traditionally the stack starts at a high address and grows downward, while the heap grows upward from lower addresses. Each thread gets its own stack; all threads in a process share one heap.
Frequently asked questions
Is the stack faster than the heap?
Yes, for allocation and usually for access. Allocating on the stack is one pointer adjustment, and stack data tends to be in the CPU cache.
How big is the stack?
It depends on the system and settings, commonly around 1 MB on Windows and 8 MB on Linux for the main thread. It can be changed, but it is always far smaller than the heap.
Are objects always on the heap?
Not always. In C++ and Rust you choose. In Java, Go and others, the compiler may place objects on the stack when escape analysis proves it is safe.
Is "the heap" related to the heap data structure?
No. The memory heap and the priority-queue heap share a name only.
Conclusion
The stack is fast, automatic and temporary; the heap is flexible, long-lived and more expensive. Knowing which one your data lives on explains why some variables are copied and others shared, why recursion can crash, and why reducing allocations is so often the first step in making code faster.
Related articles
- Why Recursion Can Crash Your Program (Stack Overflow Explained)
- How Garbage Collection Works (and Why It Sometimes Pauses Your App)
- How Virtual Memory Tricks Every Program Into Thinking It Owns the RAM
- How the CPU Cache Makes Code Fast (and How to Write Cache-Friendly Code)
