Stack and heap in Python
The usual answer, “variables live on the stack and objects on the heap”, is half right in CPython and misleading in the half that interviewers probe. Every Python object lives on the heap. What lives in a stack-like structure is the frame of each running function, and its local variables are only references into the heap.
Where those frames are kept has changed twice in recent versions, and it explains recursion limits, why a thread costs far more than a coroutine, and what a closure or generator keeps alive. Outputs are from Python 3.14.5 unless noted.
Reference counting, garbage collection and the allocator are in Python memory model — allocation, refcounting, GC.
Every object is on the heap
def area(w, h):
result = w * h # a new int object, allocated on the heap
return result
a = area(3, 4)3, 4 and 12 are all heap objects: int is an object with a header, so
sys.getsizeof(1) is 28 bytes and sys.getsizeof(2**64) is 36. There are no
unboxed values living in a stack slot the way a C int does. The names w,
h and result are slots in the function’s frame, each holding a reference
(a pointer) to an object on the heap.
So “pass by value or by reference?” has a precise answer: a call copies the references into the new frame’s slots, and both frames point at the same objects. Rebinding a name changes only that frame’s slot; mutating the object is visible through every reference (Argument Passing in Python: By Reference or By Value?).
Some heap objects are shared rather than allocated per use: small ints from
-5 to 256 are cached (int("256") is int("256") is True, 257 is not),
and since 3.12 None, True and the small ints are immortal
(Mutable vs Immutable Types in Python).
Where the frames live
A frame holds what one running call needs: its local variable slots, the evaluation stack for intermediate values, the position in the bytecode, and a link to the caller’s frame. That chain of frames is the Python call stack, the one a traceback prints.
| Version | Python frames are kept in |
|---|---|
| up to 3.10 | a heap-allocated frame object per call |
| 3.11+ | a per-thread “data stack”: chunks of memory used as a stack |
| any | a frame object built only when something asks for one |
Since 3.11 a call pushes a small internal frame onto the thread’s data stack
and pops it on return, which is a large part of why 3.11 made calls faster.
The full frame object you see from sys._getframe(), in a traceback or from a
debugger is created lazily, only when something asks for it.
Since 3.12, a call from Python to Python does not use the C stack at all:
the interpreter switches frames in a loop instead of recursing in C. The C
stack is used only when C code calls back into Python (a C extension, repr
of a container, sorted with a Python key function).
Recursion: two limits
sys.getrecursionlimit() # 1000
def f(): return f()
f() # RecursionError: maximum recursion depth exceeded- The Python-level limit is a counter of Python frames, 1000 by default,
and it protects nothing physical since 3.12: Python frames are not on the C
stack. Raising it with
sys.setrecursionlimitlets deep recursion run, at the cost of memory for the frames. - The C stack is real and fixed per thread. Recursion that goes through
C, such as
repr()of a list nested 200,000 deep, is now caught by measuring the actual stack: 3.14 reportsStack overflow (used 1952 kB) while getting the repr of an object, where 3.12 counted it against the recursion limit.
Python has no tail-call elimination, so a deep recursion that a functional
language would run in constant space builds one frame per call. Turn it into
a loop, or an explicit stack (a list used as a stack) for tree and graph
walks.
Threads have stacks; coroutines do not
Each OS thread gets its own C stack, reserved when the thread starts:
typically 8 MB of virtual address space on Linux and 1 MB on Windows, of which
only the touched pages use physical memory. threading.stack_size() returns
0, meaning “the platform default”, and can set a different size for threads
started afterwards.
A coroutine has no stack of its own. Its frame lives inside the coroutine
object on the heap, just like a generator’s: an idle coroutine object is
192 bytes on 3.14.5. A suspended await costs a heap object and whatever its
frame references, not a reserved stack, which is why a process can hold
100,000 waiting coroutines and not 100,000 threads
(asyncio and threads).
What keeps heap objects alive
Because frames hold references, anything that holds a frame holds its objects:
- A suspended generator or coroutine keeps every local it has bound (Generators).
- A closure keeps the variables it captured, in cells, after the enclosing function has returned.
- An exception traceback references the frames it passed through, and
through them every local in them. Holding a caught exception (
err = estored on an object, orsys.exc_info()kept around) keeps all of it alive; Python deletes theexcept ... as ename at the end of the block for exactly this reason.
Common misconceptions
- “Small values like ints live on the stack.” No: every value is a heap object; frames hold references.
- “Local variables are freed when the function returns.” The frame’s references are dropped; the objects are freed only if nothing else references them, by reference counting.
- “A RecursionError means the stack overflowed.” For Python-to-Python recursion since 3.12, it means the configured limit was hit; the C stack was never involved.
- “Generators are cheap because they run on the stack.” The opposite: they are cheap because their frame is on the heap and they hold no stack.
Related
Interview angle 4
- “Stack or heap: where does a Python variable live?” — the object is always on the heap. The name is a slot in the function’s frame holding a reference to it; frames form the call stack, kept since 3.11 in a per-thread data stack rather than as one heap object per call.
- “What does a RecursionError mean?” — that the Python frame counter hit
sys.getrecursionlimit()(1000). Since 3.12 Python calls do not use the C stack, so it is a policy limit; a real C-stack overflow (deeprepr, C extensions) is detected separately, by measuring the stack on 3.14.- Follow-up: “How do you process a 100,000-deep tree?” — iteratively, with an explicit stack, since Python has no tail-call elimination.
- “Why can you run 100,000 coroutines but not 100,000 threads?” — every thread reserves its own C stack (megabytes of address space); a suspended coroutine is a heap object of about 200 bytes plus what its frame references.
- “Why might a caught exception keep memory alive?” — its traceback holds
the frames it passed through, and their locals. That is why
except E as edeletesewhen the block ends.