Recursion in Python: Functions That Call Themselves
Understand recursion in Python: base cases, recursive cases, the call stack, factorial and countdown examples, plus when to use recursion instead of loops.
Key takeaways
- Recursion is a function that solves a problem by calling itself on a smaller version of the problem
- Every recursive function needs a base case that stops the calls
- The recursive case must move closer to the base case each time
- Without a base case you get infinite recursion and a RecursionError
The big idea
Recursion is when a function solves a problem by calling itself on a smaller version of the same problem. It sounds strange at first — how can something be defined in terms of itself? — but it mirrors how we think about many real tasks.
Picture a stack of boxes. To find a key hidden somewhere in the pile, you open the top box. If the key is there, great. If not, you face a smaller pile and repeat the exact same process. That repeated, shrinking process is recursion.
This lesson assumes you already understand functions that return values. If "return" is fuzzy, review that first, because recursion depends on it completely.
The two ingredients
Every correct recursive function has exactly two parts:
- Base case — the simplest situation you can answer immediately, with no further calls. It stops the recursion.
- Recursive case — where the function calls itself on a smaller input, getting closer to the base case each time.
Miss the base case and the function calls itself forever, producing a RecursionError.
A gentle first example: countdown
def countdown(n):
if n == 0: # base case
print("Blast off!")
else: # recursive case
print(n)
countdown(n - 1) # call self on a smaller number
countdown(3)
Output:
3
2
1
Blast off!
Line by line:
if n == 0:is the base case. Whennreaches 0, we print "Blast off!" and do not call again. The chain stops.- Otherwise we print
n, then callcountdown(n - 1). Each call uses a number one smaller, so we march toward 0.
Trace it: countdown(3) prints 3 then calls countdown(2), which prints 2 then calls countdown(1), which prints 1 then calls countdown(0), which hits the base case.
Worked example: factorial
The factorial of a number n (written n!) is n × (n-1) × (n-2) × ... × 1. For example 4! = 4 × 3 × 2 × 1 = 24. Mathematicians define it recursively:
0! = 1(base case)n! = n × (n-1)!(recursive case)
That definition translates straight into code:
def factorial(n):
if n == 0: # base case
return 1
else: # recursive case
return n * factorial(n - 1)
print(factorial(4)) # 24
Let's trace factorial(4) carefully. Each call waits for the one below it to return:
factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 * factorial(0)
factorial(0) = 1 <- base case returns directly
Now the answers travel back up:
factorial(1) = 1 * 1 = 1
factorial(2) = 2 * 1 = 2
factorial(3) = 3 * 2 = 6
factorial(4) = 4 * 6 = 24
This "go down to the base case, then come back up" pattern is the heart of recursion. The list of paused calls waiting to finish is called the call stack.
Recursion vs loops
The same factorial can be written with a loop:
def factorial_loop(n):
result = 1
for i in range(1, n + 1):
result = result * i
return result
print(factorial_loop(4)) # 24
Both are correct. So when do you reach for recursion?
- Use a loop for straightforward repetition like counting or summing a list — it is usually simpler and uses less memory.
- Use recursion when the problem naturally splits into smaller copies of itself: navigating nested folders, walking a family tree, or processing nested lists. You will meet these structures in Python nested lists and dictionaries.
A practical recursive example: summing a nested list
def deep_sum(items):
total = 0
for item in items:
if isinstance(item, list): # a smaller list inside
total += deep_sum(item) # recurse into it
else:
total += item
return total
print(deep_sum([1, [2, 3], [4, [5, 6]]])) # 21
Here the base case is hidden: when a list contains no more lists, the for loop finishes and we return the total without recursing further. Each nested list triggers a deeper call. A plain loop cannot easily handle unknown levels of nesting, which is exactly where recursion shines.
Common mistakes
- No base case, or a base case the recursion never reaches. Always ask: "Does my input definitely get smaller and hit the stopping point?"
- Forgetting to return the recursive call.
n * factorial(n - 1)must be returned; otherwise the function gives backNone. - Going too deep. Python limits recursion to roughly 1000 calls. Very deep problems may need a loop instead.
Try it yourself
- Write a recursive
sum_to(n)that adds1 + 2 + ... + n. Base case:sum_to(0)returns 0. - Write a recursive
power(base, exp)that computesbase ** expusingbase * power(base, exp - 1), with base casepower(base, 0) = 1. - Write a recursive function that counts how many times a letter appears in a string by checking the first character, then recursing on the rest.
When you finish, strengthen the looping alternative with Python for loops so you can choose the right tool for each job.
Quick quiz
Test yourself and earn XP
What is a recursive function?
A recursive function calls itself, usually on a smaller piece of the same problem.
What is the job of the base case?
The base case is the simplest situation that can be answered directly, so the chain of calls stops.
What happens if a recursive function has no base case?
With no base case, the calls never stop, and Python eventually raises a RecursionError.
For factorial, what is factorial(0)?
By definition factorial(0) is 1, which is the base case that stops the recursion.
In the recursive case, the function should call itself on...
Each recursive call must shrink the problem so it eventually reaches the base case.
FAQ
Not always. Some problems (like exploring nested folders or tree structures) are much clearer with recursion, while simple counting is usually easier with a loop. Both can solve many of the same problems.
It is the error Python raises when a function calls itself too many times, usually because the base case is missing or never reached. Python has a recursion limit (about 1000 calls by default) to prevent crashes.
It is Python's internal record of which function calls are still waiting to finish. Each recursive call adds a frame to the stack; when a call returns, its frame is removed.
Keep exploring
More in Coding