Recursion
Learn how a function can call itself to solve problems, understand base cases, and see how the call stack tracks recursive calls.
Introduction
Some problems are naturally defined in terms of smaller versions of themselves. A folder contains files and other folders, and each of those folders contains more files and folders — the structure repeats. Recursion is the programming technique that mirrors this idea: a function that calls itself to solve a smaller piece of the same problem.
In this lesson, you will learn what recursion is, why every recursive function needs a base case, how Python tracks recursive calls using the call stack, and when recursion is a better choice than a loop.
- What recursion means and how a function calls itself.
- The difference between a base case and a recursive case.
- How to trace a recursive call using the call stack.
- Python's recursion depth limit and why it exists.
- When to choose recursion over a loop, and vice versa.
What is Recursion?
Recursion happens when a function calls itself, either directly or indirectly, in order to solve a problem. Instead of solving the whole problem in one step, a recursive function solves a small piece of it and then hands off a slightly smaller version of the same problem to another call of itself.
def countdown(n):
print(n)
if n > 0:
countdown(n - 1)
countdown(3)3
2
1
0Each call to countdown() prints the current number and then calls countdown() again with a smaller number. Eventually n reaches 0, the condition n > 0 becomes False, and the chain of calls stops.
Self-Calling
A recursive function contains a call to itself somewhere in its body.
Shrinking Problem
Each call should work on a smaller version of the original problem.
Stopping Condition
A base case eventually stops the chain of calls from continuing forever.
Base Case & Recursive Case
Every correct recursive function has two essential parts.
Base Case
The condition that stops the recursion. It is solved directly without calling the function again.
Recursive Case
The part where the function calls itself with a smaller or simpler version of the problem.
Without a base case (or with one that is never reached), a recursive function will call itself forever, eventually causing Python to raise a RecursionError.
def recursive_function(n):
if n == 0: # base case
return 0
else: # recursive case
return n + recursive_function(n - 1)When writing a recursive function, decide the base case before writing the recursive case. This helps you avoid infinite recursion right from the start.
Example: Factorial
The factorial of a number n (written n!) is the product of all positive integers from 1 up to n. Factorial is a classic example because it is naturally recursive: 5! is just 5 times 4!.
def factorial(n):
if n == 0 or n == 1: # base case
return 1
return n * factorial(n - 1) # recursive case
print(factorial(5))120factorial(5) calls factorial(4), which calls factorial(3), and so on, until factorial(1) returns 1 directly. Then each pending multiplication completes on the way back up.
Tracing the Call Stack
Every time a function is called, Python pushes a new "frame" onto the call stack to keep track of that call's local variables and where it should return to. Recursive calls stack up the same way — the calls go "down" until the base case is hit, then results are returned "up" the stack one level at a time.
factorial(4)
→ factorial(3)
→ factorial(2)
→ factorial(1) returns 1
factorial(2) returns 2 * 1 = 2
factorial(3) returns 3 * 2 = 6
factorial(4) returns 4 * 6 = 24The calls grow downward until factorial(1) hits the base case, and then each waiting call multiplies its own n by the result it receives, unwinding the stack back to the original call.
Think of the call stack like a pile of plates. Each recursive call adds a plate on top. The base case is reached when you cannot add more plates — then you start removing plates from the top, one at a time, combining results as you go.
Example: Fibonacci Sequence
The Fibonacci sequence is another common recursion example: each number is the sum of the two numbers before it (0, 1, 1, 2, 3, 5, 8, ...).
def fibonacci(n):
if n <= 1: # base case
return n
return fibonacci(n - 1) + fibonacci(n - 2) # recursive case
for i in range(8):
print(fibonacci(i), end=" ")0 1 1 2 3 5 8 13This simple version of fibonacci() recalculates the same values many times, so it becomes very slow for larger n (like 35 or above). Later lessons on memoization and dynamic programming show how to fix this.
Example: Summing a List Recursively
Recursion is not limited to numbers — it also works well on sequences like lists, where the "smaller problem" is simply a shorter list.
def sum_list(numbers):
if not numbers: # base case: empty list
return 0
return numbers[0] + sum_list(numbers[1:]) # recursive case
print(sum_list([4, 8, 15, 16, 23]))66Each call adds the first element to the sum of the rest of the list, until the list becomes empty and the base case returns 0.
Recursion Depth Limit
Python protects itself from infinite recursion by limiting how many recursive calls can be stacked at once. This is called the recursion limit, and by default it is 1000 calls.
import sys
print(sys.getrecursionlimit())1000If a recursive function goes past this limit — usually because the base case is missing or unreachable — Python raises a RecursionError.
def broken(n):
return broken(n + 1) # never stops
broken(1)RecursionError: maximum recursion depth exceededIt is possible to raise the limit with sys.setrecursionlimit(), but this is rarely a good fix — it usually just delays the crash and risks exhausting your computer's actual memory stack. It is almost always better to fix the logic or switch to iteration.
Recursion vs Iteration
Almost anything you can write recursively can also be written with a loop, and vice versa. Choosing between them is about readability and the shape of the problem, not just correctness.
Recursion
- Reads naturally for problems defined in terms of themselves (trees, nested folders, divide-and-conquer).
- Can be more concise and expressive for such problems.
- Uses extra memory for each call on the stack.
- Limited by Python's recursion depth (default 1000).
Iteration
- Usually faster and uses constant memory (no growing call stack).
- Better suited for simple repeated tasks like counting or summing.
- Can look messier for naturally recursive structures like trees.
- No risk of hitting a recursion depth error.
def factorial_iterative(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
print(factorial_iterative(5))120Prefer recursion when the problem is naturally hierarchical or self-similar (like traversing a tree or nested directories). Prefer iteration for simple, flat, repetitive tasks where performance and memory matter more.
Common Mistakes
- Forgetting the base case entirely, causing infinite recursion.
- Writing a base case that is never actually reached (e.g., checking n == 0 but only ever decreasing by 2 from an odd number).
- Not reducing the problem size in the recursive call, so it never approaches the base case.
- Using plain recursion for problems like Fibonacci without considering how slow repeated calls can get.
- Raising sys.setrecursionlimit() instead of fixing a genuinely broken base case.
Best Practices
- Always identify and write the base case before the recursive case.
- Make sure every recursive call moves closer to the base case.
- Keep recursive functions focused on one clear, small task.
- Trace small examples by hand (or on paper) to verify the call stack behaves as expected.
- Switch to an iterative solution if recursion depth or performance becomes a problem.
Frequently Asked Questions
What happens if a recursive function has no base case?
It will keep calling itself until Python's recursion limit is reached, then raise a RecursionError.
Is recursion slower than iteration?
Generally yes, because each call adds overhead and uses stack memory. For simple tasks, an equivalent loop is usually faster and uses less memory.
Can I increase Python's recursion limit?
Yes, with sys.setrecursionlimit(), but this is rarely recommended — it just delays a crash rather than fixing the underlying issue, and can crash Python itself if it exceeds what your system's stack can hold.
What is the call stack?
It is the internal structure Python uses to keep track of function calls that are currently in progress, including recursive calls. Each call adds a frame; returning removes it.
Should I always use recursion for tree-like or nested data?
It is usually a natural fit, but always confirm the base case is correct and consider the depth of the data — deeply nested structures can still hit the recursion limit.
Key Takeaways
- Recursion is when a function calls itself to solve a smaller version of the same problem.
- Every recursive function needs a base case (to stop) and a recursive case (to progress).
- Python tracks recursive calls using the call stack, which grows on the way down and unwinds on the way back up.
- Python has a default recursion limit of 1000 calls, after which it raises a RecursionError.
- Recursion suits naturally self-similar problems; iteration is often simpler and faster for flat, repetitive tasks.
Summary
Recursion lets a function solve a problem by calling itself on smaller pieces of that same problem, stopping once it reaches a clearly defined base case. Tracing the call stack — calls going down, results returning up — is the key to understanding how recursive functions actually execute.
In this lesson, you saw recursion applied to factorial, Fibonacci, and summing a list, learned about Python's recursion depth limit, and compared recursion with iteration to know when each is the better tool.
- You understand what recursion is and how a function calls itself.
- You can identify base cases and recursive cases.
- You can trace a recursive call through the call stack.
- You know Python's recursion limit and when to prefer iteration instead.