Stack vs heap in C++: where your variables actually live
Short answer: ordinary local variables live on the stack β allocated in nanoseconds, destroyed automatically when their scope ends. The heap is for things that must outlive their scope or be sized at runtime β bigger and flexible, but allocated explicitly and cleaned up by you.
Watch the lifetime difference run
The function below has a local variable and the program has a global. Call the function three times and watch what each remembers:
#include <iostream>
using namespace std;
int counter = 0; // global: lives for the whole program
void bump() {
int local = 0; // stack: recreated on every call
local++;
counter++;
cout << "local=" << local << " counter=" << counter << endl;
}
int main() {
bump();
bump();
bump();
return 0;
}
local prints 1 every single time β it's born, lives and dies within one call of bump(), on the stack. counter keeps its value across calls because its lifetime is the whole program. Lifetime is the heart of the stack/heap distinction.
Side by side
| Stack | Heap | |
|---|---|---|
| Who allocates | Automatic β just declare it | You β new (or a container/smart pointer) |
| Lifetime | Ends when the scope ends | Until you delete it (or its owner dies) |
| Speed | Extremely fast (one pointer move) | Slower (bookkeeping, searching for space) |
| Size | Small β typically ~1β8 MB | Basically "available RAM" |
| Dies when? | Scope exit β guaranteed | Only if you free it β else: leak |
| Typical use | Locals, function parameters | Big data, runtime-sized data, shared objects |
Heap allocation: new and delete
(Our in-browser engine doesn't support new/delete yet β run this one on your desktop compiler.)
int main() {
int* p = new int(42); // allocate on the heap, p holds the address
cout << *p << endl; // 42
delete p; // give the memory back β your job, not the scope's
return 0;
}
Every new must be matched by exactly one delete. Miss the delete β memory leak (memory you can never reach or reuse). delete twice, or keep using the pointer after deleting (a dangling pointer) β undefined behavior.
The modern answer: you rarely call new/delete by hand
C++'s superpower for this problem is RAII β Resource Acquisition Is Initialization: tie the resource's lifetime to an object's scope, and the scope guarantees cleanup. You've already used RAII without the name: a std::vector manages its heap array automatically. For single objects, the same job is done by smart pointers:
#include <memory>
int main() {
auto p = std::make_unique<int>(42); // heap memory, owned by p
cout << *p << endl; // 42
return 0; // p dies here β memory freed automatically
}
Rule of thumb for modern code: stack by default; heap through an owner (vector, string, unique_ptr) β almost never raw new/delete.
Two famous failure modes
- Stack overflow: the stack is small. A recursion that never reaches its base case (or huge local arrays like
int big[10000000]) exhausts it β the program crashes. That's literally where the website name Stack Overflow comes from. - Returning a pointer to a local:
int* f() { int x = 5; return &x; }βxdies at the closing brace; the caller receives an address of a corpse. Undefined behavior, and the single most classic lifetime bug in C++ interviews.
Interview questions on this theme
- "What lives on the stack vs the heap?" β locals/parameters vs explicitly allocated data (and what vectors/strings manage for you under the hood).
- "Why is stack allocation fast?" β it's just moving one stack pointer; heap allocation needs bookkeeping and a search for free space.
- "What is a memory leak, in one sentence?" β heap memory that's unreachable and will never be freed.
- "What is RAII?" β bind resource lifetime to object scope so cleanup is automatic; it's how vector, string, fstream and smart pointers all work.
- "Why is returning a reference/pointer to a local variable broken?" β the lifetime question that starts this article, weaponized.
Practice in the playground
- Modify the lifetime demo: add a second global and a second local; predict all six output lines before running.
- Write
int* broken() { int x = 7; return &x; }β don't run it (undefined behavior!); instead, explain in one comment line why a desktop compiler warns about it. - Recursion depth: the Fibonacci example with
fib(10)is safe; reason about roughly how deepfib's stack goes and whyfib(100000)would be a stack overflow.
Related: pass by value vs pointer vs reference, what int main() is, 15 beginner interview questions.