You call a function, it runs, and then the program carries on exactly where it left off. That sounds obvious, but think about it: the computer has to remember where to come back to. And a function can call another function, which calls another. How does it keep track of all those "come back here" notes without getting lost?

The answer is a structure called the call stack. By the end of this article you will be able to read it, draw it, and explain the error message that appears when it fills up.

A stack of sticky notes

Imagine you are reading a book and a word sends you to a dictionary. Inside the dictionary a definition sends you to an encyclopedia. You keep a sticky note at each place so you can find your way back. When you finish with the encyclopedia you throw away the top note and return to the dictionary. The note you made last is the first one you use. That "last in, first out" rule is what a stack is.

Each call to a function puts one note on the pile. The note is called a frame. A frame holds the function's own variables (its arguments and local variables) and the spot in the code to return to. When the function finishes, its frame is removed and the program continues in the frame below.

Watching a call stack grow and shrink

Here is a small Python function. It prints a message, calls itself with a smaller number, and prints again:

def countdown(n):
    print("start", n)
    if n > 0:
        countdown(n - 1)
    print("end  ", n)

countdown(2)

Running it (Python 3.13) prints:

start 2
start 1
start 0
end   0
end   1
end   2

Notice the order. It goes down through 2, 1, 0 and then comes back up through 0, 1, 2. The "end" lines come in reverse because the most recent call has to finish first. A function that calls itself like this is called recursive, and the call stack is what makes it work.

Try it: step through factorial

The factorial of a number multiplies it by every whole number below it: 4! is 4 × 3 × 2 × 1 = 24. Written recursively, factorial(4) is 4 times factorial(3), and so on, until we reach 1. Press Step and watch the code on the left and the stack on the right. Change the starting number to see a taller or shorter stack.

4

code

def factorial(n):    if n <= 1:        return 1    return n * factorial(n - 1)

call stack (newest on top)

Each call adds a frame on top; each return removes the top frame and hands a value down to the frame below.

Look at what happens at the bottom of the recursion. factorial(1) returns 1 straight away without calling anything. That is the base case: the condition that stops the chain. Then the frames are popped one by one, and each waiting frame can finally finish its multiplication. Every frame had its own separate n, which is why 4, 3, 2 and 1 can all exist at once without colliding.

The same idea shows up in error messages

When something goes wrong, Python prints the call stack as it was at that moment. This is called a traceback. Take three functions that call each other, and the innermost one fails:

def c():
    raise ValueError("boom")
def b():
    c()
def a():
    b()
a()

Python prints (newer versions also add ^ marker lines under each source line, which I left out):

Traceback (most recent call last):
  File "d.py", line 7, in <module>
    a()
  File "d.py", line 6, in a
    b()
  File "d.py", line 4, in b
    c()
  File "d.py", line 2, in c
    raise ValueError("boom")
ValueError: boom

That is the stack, listed oldest frame first and newest last. "Most recent call last" means exactly that. The bug is almost always at the bottom, and the lines above show how the program got there. Once you see a traceback as a stack, reading one gets much easier.

When the stack runs out of room

Every frame takes a little memory, and the stack has a limit. What if a function calls itself and never reaches a base case?

import sys
print(sys.getrecursionlimit())

def forever(n):
    return forever(n + 1)

try:
    forever(0)
except RecursionError as e:
    print(type(e).__name__, "-", e)

Output on Python 3.13:

1000
RecursionError - maximum recursion depth exceeded

Python protects you by stopping at a depth of 1000 by default and raising a RecursionError instead of using up all the memory. Other languages behave differently: in many of them, running out of stack space is called a stack overflow, and that is where the name of the famous website comes from. The exact limit depends on the language and the system, so treat 1000 as Python's default rather than a universal number.

So what does this mean for your code?

First, when a program crashes with a long traceback, read it from the bottom up. The last line says what went wrong, and the frames above it tell you the path that led there.

Second, every recursive function needs a base case, and each call must move toward it. In factorial, n - 1 shrinks toward 1. If you forget the shrinking step, you get the RecursionError above.

Third, recursion is a choice, not a requirement. The same factorial can be written with a loop, which uses one frame no matter how big n is:

def fact_loop(n):
    r = 1
    for i in range(2, n + 1):
        r *= i
    return r

print(fact_loop(4))   # 24

Recursion shines when the problem naturally looks like smaller copies of itself, such as folders inside folders or branches of a tree. For plain counting, a loop is simpler and safer. Either way, you now know what the computer is doing behind the scenes: a pile of sticky notes, growing on every call and shrinking on every return.