Stack vs Heap
Every program needs memory to store data, execute functions, and manage objects. But not all memory is managed in the same way. Two important concepts behind memory management are the stack and the heap.
Understanding these concepts helps explain how variables work, why some data disappears when a function returns, and how programming languages manage memory.
Let’s explore them using simple analogies and examples.
What Are the Stack and Heap?
Imagine you are working in an office.
- The stack is like your desk. You place the documents you are currently working on on top. When you finish a task, you put those documents away.
- The heap is like a storage room. You store items there that may need to be used later, shared with others, or kept for an unpredictable amount of time.
A program uses memory in a similar way.
The stack is commonly used to manage function calls and their local data. The heap is commonly used for dynamically allocated data, such as collections, strings, and objects that may need to outlive a particular function call.
Understanding the Stack
The stack follows a principle called LIFO (Last In, First Out).
Think of a stack of plates: the last plate you put on top is the first one you remove.
When a function is called, the program typically creates a stack frame containing the information needed for that call. When the function returns, its frame is removed.
def greet():
message = "Hello, World!"
print(message)
greet()When greet() executes:
- A function call becomes active.
- The variable
messagerefers to the string"Hello, World!". - The string is printed.
- The function returns, and its local variable is no longer accessible by that name.
Conceptually, the call stack looks like this:
During the function call:
┌─────────────────────┐
│ greet() │
│ message │
│ "Hello, World!" │
├─────────────────────┤
│ Calling function │
└─────────────────────┘
After greet() returns:
┌─────────────────────┐
│ Calling function │
└─────────────────────┘The diagram illustrates the lifetime of a function call, not the exact physical memory layout.
Let’s understand this with another in-depth example:
def second():
y = 20
print(y)
def first():
x = 10
second()
print(x)
first()
1. first() starts
The variable x = 10 is created inside first().
STACK
┌─────────────┐
│ first() │
│ x = 10 │
└─────────────┘2. first() calls second()
Now second() starts, and y = 20 is created. Its frame goes on top of first().
STACK
┌─────────────┐
│ second() │ ← Top
│ y = 20 │
├─────────────┤
│ first() │
│ x = 10 │
└─────────────┘3. second() finishes
It prints 20, then its frame is removed. Execution returns to first().
STACK
┌─────────────┐
│ first() │ ← Top
│ x = 10 │
└─────────────┘4. first() finishes
It prints 10, then its frame is removed.
STACK
┌─────────────┐
│ Empty │
└─────────────┘OUTPUT:
20
10Remember: first() waits while second() runs. Once second() finishes, Python returns to first() and continues from where it stopped.
When is the stack useful?
The stack is particularly useful for:
- Managing function calls and return information.
- Storing many local values with predictable lifetimes.
- Handling nested function calls efficiently.
- Automatically managing temporary call-related storage.
However, the stack has limited capacity. Excessively deep recursion or very large stack allocations can cause a stack overflow.
A stack frame is the information needed to manage one active function call.
Get Data with Abhishek’s stories in your inbox
Join Medium for free to get updates from this writer.
A stack overflow happens when a program exceeds its available call-stack capacity or reaches a runtime’s call-depth limit.
A common cause is infinite or excessively deep recursion.
def call_forever():
call_forever()
call_forever()call_forever() — Call 1
call_forever() — Call 2
call_forever() — Call 3
call_forever() — Call 4
Call-depth or stack limit exceededTo avoid this problem, use a proper stopping condition for recursive functions or consider an iterative solution when appropriate.
def countdown(n):
while n > 0:
print(n)
n -= 1
print("Done!")
countdown(5)Understanding the Heap
Now that we understand the stack, let’s look at the heap.
The heap is a region of memory commonly used for dynamically allocated data. Unlike the call stack, which follows the structure of active function calls, heap allocations can remain available after the function that created them has returned.
The heap is useful when data needs a lifetime that does not match a single function call.
For example, consider a program that creates a list of user records, a collection of products, or a large data structure. These objects may need to be accessed by multiple parts of the program.
The heap provides a way to store such dynamically allocated objects.
Example: Creating an Object
Consider this Python code:
def create_numbers():
numbers = [10, 20, 30]
return numbers
result = create_numbers()
print(result)[10, 20, 30] # outputWhat happens here?
- The function
create_numbers()is called. - A list object containing
10,20, and30is created. - The local variable
numbersrefers to that list. - The function returns a reference to the list.
- The variable
resultreceives that reference. - The list remains accessible after the function returns.
Conceptually, we can represent the situation like this:
While the function is running:
CALL STACK HEAP
┌──────────────┐ ┌─────────────────┐
│ create_ │ │ │
│ numbers() │ │ │
│ │ │ │
│ numbers ─────┼───────→│ [10, 20, 30] │
└──────────────┘ └─────────────────┘After the function returns:
CALL STACK HEAP
┌──────────────┐ ┌─────────────────┐
│ Main code │ │ │
│ │ │ │
│ result ──────┼───────→│ [10, 20, 30] │
└──────────────┘ └─────────────────┘The function’s local name numbers no longer exists after the function returns, but the list remains accessible through result.
Important: These diagrams illustrate the conceptual relationship between references and objects. Python does not guarantee that every variable lives on the physical call stack or that every object is allocated in a particular physical heap region.
Why Doesn’t the List Disappear?
The key idea is that a variable and the object it refers to are not necessarily the same thing.
In the example above:
numbersis a local name insidecreate_numbers().- The list is an object.
resultreceives a reference to that same object.
When the function returns, its local name disappears. However, the list is still reachable through result.
This is why data can outlive the function that created it.
When Is the Heap Useful?
The heap is particularly useful for:
- Creating objects and collections whose lifetimes are not tied to one function call.
- Storing dynamically sized data structures.
- Sharing objects between different parts of a program.
- Building linked lists, trees, graphs, and other dynamic structures.
- Managing data that needs to remain accessible after a function returns.
If the stack has a fixed size, how can functions have different sizes?
Here’s the important distinction:
- Stack capacity: The total amount of stack memory available to a thread is limited.
- Stack frame size: The amount of memory needed for one active function call can vary.
- Total stack usage: The combined memory used by all active calls and their frames.
Think of the stack as a suitcase with a fixed capacity. You can put small or large items into it, but the suitcase itself does not grow indefinitely.








