From Call Stack to Heap: How Python Manages Execution and Data in Memory

When Python runs your code, it manages memory using two key areas:
π§ Stack v/s Heap?
π§ The Stack
The call stack is a contiguous block of memory reserved by the operating system at program start.
It grows and shrinks as functions are called and return.
It operates on a LIFO (Last In, First Out) model.
Stack memory is thread-local and automatically managed β allocation is a simple pointer increment; deallocation is a pointer decrement.
Function calls store their stack frames here β which include local variables and return addresses.
| Question | Answer | | --- | --- | | Where is the stack memory? | In the processβs virtual memory, near the top (high addresses) | | Who allocates it? | The operating system, at process or thread creation | | How is it used? | Managed using CPU stack pointer, grows/shrinks with function calls | | How big is it? | OS-defined, typically 1β8MB per thread (configurable) |
π© The Heap
The heap is an entirely separate region of memory provided by the OS. It is:
A region of virtual memory reserved by the OS for long-term dynamic allocation.
Allocations from the heap are done via manual control or an allocator (Python handles this behind the scenes). i.e Not automatically structured like the stack.
Heap memory is usually backed by pages, and the OS maps these into your process address space via system calls like
mmap()
.Allocated and deallocated using system calls (e.g.,
malloc
/free
in C, orbrk
/mmap
at the OS level).Managed by a dynamic memory allocator β in Pythonβs case, pymalloc (for small objects) or the OS (for large ones).
Not tied to the function call stack β meaning:
Objects on the heap persist beyond the function call that created them.
Any scope can reference them (global, local, etc.)
| Question | Answer | | --- | --- | | Where is the heap memory? | In the processβs virtual memory, typically located below the stack, growing upward | | Who allocates it? | The operating system, via system calls (
brk
,mmap
) when requested by Pythonβs memory manager | | How is it used? | Dynamically allocates and frees memory for objects like lists, dicts, classes | | How big is it? | Limited by available virtual memory or system limits (can be GBs in size) | | Who manages cleanup? | Pythonβs garbage collector and reference counting automatically free unused memory | | Is access fast? | Slower than stack β requires pointer dereferencing and memory bookkeeping |
π§΅ Memory Map of a Python Process
When a program runs (including a Python script), the operating system creates a process with a virtual memory layout that looks roughly like this:
HIGH MEMORY ADDRESSES
+-----------------------------+
| Stack | β grows down
+-----------------------------+
| Heap (malloc/pymalloc)| β grows up
+-----------------------------+
| Global/Static Variables |
+-----------------------------+
| Code Segment |
+-----------------------------+
LOW MEMORY ADDRESSES
The stack starts near the top of the virtual address space and grows downward.
The heap starts after the static data and grows upward.
This design prevents them from immediately colliding and helps the OS detect overflows.
4. π§ Stack vs Heap at the Hardware Level
Stack:
Managed using a dedicated stack pointer register (
RSP
/ESP
) that always points to the top of the stack.CPU instructions like
call
,ret
,push
, andpop
directly manipulate the stack and this register.Though managed by the OS and CPU, the stack is backed by real RAM via the processβs virtual address space.
Heap:
The heap has no dedicated CPU register.
Memory is allocated at runtime via system calls like
brk()
ormmap()
, often through functions likemalloc()
or Pythonβs memory manager.Allocation returns a pointer to the memory; it's up to the program to manage it.
Access is indirect β you follow pointers to interact with heap data.
There's no automatic structure like push/pop; memory must be explicitly managed (or garbage collected in Python).
Like the stack, heap memory is also backed by physical RAM, but is dynamically mapped in the processβs address space.
Feature | Stack | Heap |
CPU Register | Uses dedicated stack pointer (RSP /ESP ) | β No dedicated register |
Access Method | Directly accessed via push , pop , call , ret instructions | Accessed via pointers returned from allocators like malloc |
Memory Management | Automatically grows/shrinks with function calls and returns | Must be manually managed (or via garbage collection in Python) |
Structure | Linear LIFO (Last In First Out) | No fixed structure β random access possible |
Allocation System Calls | Reserved at process/thread start | Uses brk() , mmap() under the hood |
Persistence | Temporary β tied to function lifetime | Long-lived β survives beyond function calls |
Backed By | Real RAM via virtual memory | Real RAM via virtual memory |
π What Happens When a Function Is Called?
πΉ 1. The Call Stack
The call stack keeps track of function calls. Each time a function is invoked, Python creates a stack frame for that function. This frame contains:
The functionβs parameters
All local variables
π§ͺ Example:
def add(x, y):
result = x + y
return result
def main():
a = add(2, 3)
print(a)
main()
π Stack Behavior
Letβs trace the execution of the program step-by-step and visualize how the call stack and local variables evolve.
βΆοΈ Step 1: main()
is called (Line 8)
Call Stack:
ββββββββββββββ
β main() β
ββββββββββββββ
Local Variables:
main: a = ?
βΆοΈ Step 2: add(2, 3)
is called (Line 5)
Call Stack:
ββββββββββββββ
β add(2, 3) β
ββββββββββββββ€
β main() β
ββββββββββββββ
Local Variables:
add: x = 2, y = 3, result = ?
βΆοΈ Step 3: Inside add()
, result = x + y
(Line 2)
Call Stack:
ββββββββββββββ
β add(2, 3) β
ββββββββββββββ€
β main() β
ββββββββββββββ
Local Variables:
add: x = 2, y = 3, result = 5
βΆοΈ Step 4: return result
(Line 3) β back to main()
Call Stack:
ββββββββββββββ
β main() β
ββββββββββββββ
Local Variables:
main: a = 5
βΆοΈ Step 5: print(a)
is executed (Line 6)
π¨οΈ Output:
5
Call Stack remains:
ββββββββββββββ
β main() β
ββββββββββββββ
β
Step 6: End of main()
Call Stack: (empty)
The program completes and all memory is cleaned up.
Local variables like x
, y
, result
, and a
exist only inside their respective function frames. Once the function finishes, they're gone.
πΆ 2. The Heap
While the call stack handles how functions are executed, Python uses a separate memory area β the heap β to store the data those functions operate on. Whenever you create complex objects like lists, dictionaries, or class instances, Python places them in the heap so they can persist beyond a single function call, be shared across scopes, and grow dynamically.
π¦ How Python Stores Data on the Heap
Weβve seen how Python uses the call stack to manage function execution. But what happens when we create data β like a list β that persists or is shared between scopes?
Letβs walk through it with an example function:
π§ Example: Heap Allocation in Action
def make_shopping_list():
shopping_list = ['apples', 'bananas']
return shopping_list
cart = make_shopping_list()
π§ What Happens in Memory?
πΉ Step 1: make_shopping_list()
is called
Python creates a stack frame for the function.
Inside the function, a list object
['apples', 'bananas']
is created and stored in the heap.The variable
shopping_list
(just a reference) lives in the stack frame.
Stack (during function):
ββββββββββββββββββββββββββββββ
β make_shopping_list() β
β β shopping_list ββββββ β
βββββββββββββββββββββββββββββ
βΌ
Heap:
[ 'apples', 'bananas' ] β list object
β β
'apples' 'bananas' β individual string objects (also in heap)
πΉ Step 2: Function returns the list
The reference to the list object is returned.
The local variable
shopping_list
is discarded when the stack frame is popped.Outside the function,
cart
now points to the same list.
Stack (after return):
βββββββββββββββββ
β cart ββββββ β
βββββββββββββββββ
βΌ
Heap:
[ 'apples', 'bananas' ] β still lives in heap
β β
'apples' 'bananas'
π Why Is shopping_list
in the Stack?
Variable names like
shopping_list
andcart
live in their respective namespaces:Inside functions β in the call stack
Globally β in the global namespace
They don't hold data themselves β they hold references (pointers) to the actual data, which lives in the heap.
β Quick Recap
Component | Location | Explanation |
'apples' , 'bananas' | Heap | Immutable string objects |
['apples', 'bananas'] | Heap | Mutable list object that holds references |
shopping_list | Stack | Local variable inside function (temporary reference) |
cart | Stack/Global | Variable in outer scope (holds same reference) |
π§ Why Use the Heap?
The list object needs to outlive the function that created it.
By storing it in the heap, Python ensures the object remains alive as long as thereβs a reference to it.
When no variable points to the object anymore, Pythonβs garbage collector reclaims the memory.
In Python, understanding how the stack handles execution and how the heap manages data is key to writing efficient, bug-free code that truly respects memory.
Subscribe to my newsletter
Read articles from Madhura Anand directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Madhura Anand
Madhura Anand
Data Professional with experience in civic data, AI applications, and building data pipelines to solve real-world problems. My work with government datasets has given me unique domain knowledge of how AI can drive operational efficiency and informed decision-making. Iβm now focused on bringing this expertise to AI-driven companies, with a passion for building products with a purpose turning complex data into solutions that matter. π§ madhura.anand@outlook.com Disclaimer: All opinions and views expressed in any posts/blogs are my own and do not reflect the views or values of my organization.