Sitemap
Data with Abhishek

Data with Abhishek

Stack vs Heap

5 min readOct 27, 2026

--

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:

  1. A function call becomes active.
  2. The variable message refers to the string "Hello, World!".
  3. The string is printed.
  4. 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
10

Remember: 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 exceeded

To 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] # output

What happens here?

  1. The function create_numbers() is called.
  2. A list object containing 10, 20, and 30 is created.
  3. The local variable numbers refers to that list.
  4. The function returns a reference to the list.
  5. The variable result receives that reference.
  6. 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:

  • numbers is a local name inside create_numbers().
  • The list is an object.
  • result receives 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.

--

--