Recursion
Recursion is one of the most powerful—and most misunderstood—ideas in programming.
Many candidates can write recursive code, but struggle to explain:
- Why it works
- What happens behind the scenes
- Why it sometimes crashes with a stack overflow
This lesson focuses on clarity, intuition, and mental models, so recursion stops feeling like magic and starts feeling predictable.
What Is Recursion?
Recursion is when a function calls itself to solve a smaller version of the same problem.
Instead of solving the entire problem at once, you:
- Solve one piece of the problem
- Delegate the rest to a recursive call
Every recursive solution must have two parts.
Think Functions
What is a function? A container that produces an output based on some input. With recursion, you can generate the output for some function(A) = some_logic combined with function(B).
The Two Required Parts of Recursion
1. Base Case (Stopping Condition)
The base case tells the function when to stop recursing.
Without a base case:
- The function calls itself forever
- The program eventually crashes
2. Recursive Case
The recursive case:
- Calls the same function
- Uses a smaller or simpler input
- Moves closer to the base case
If the problem does not get smaller, recursion will not terminate.
A Simple Mental Model
Think of recursion as delegation:
“I’ll do my small part of the work,
and trust the same function to handle the rest.”
Each function call:
- Handles one step
- Passes the remaining work to the next call
Classic Example: Factorial
Definition:
5! = 5 × 4 × 3 × 2 × 1
Recursive idea:
5! = 5 × 4!4! = 4 × 3!- ...
1! = 1← base case
Recursive Structure

factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
Notice:
- The problem size decreases (
n - 1) - There is a clear stopping point
What Actually Happens: The Call Stack
This is the most important concept to understand recursion.
Every function call:
- Is pushed onto the call stack
- Stores its own local variables and execution state
Example: factorial(3)
Call stack grows like this, function calls are pushed into the stack with inputs:
factorial(3)
→ factorial(2)
→ factorial(1)
Once the base case returns, functions are popped and outputs get computed:
factorial(1) returns 1
factorial(2) returns 2
factorial(3) returns 6
This process is called stack unwinding.
Stack Growth vs Stack Unwinding
Stack Growth (Going Down)
- Recursive calls are being added
- No results yet
- Memory usage increases
Stack Unwinding (Coming Back Up)
- Base case is reached
- Return values flow upward
- Final answer is computed
Many recursion bugs happen because candidates lose track of which phase they are in.
Stack Overflow (Why Recursion Can Crash)
A stack overflow occurs when:
- Too many recursive calls are made
- The base case is missing or incorrect
- The input is too large
Example of a broken recursion:
recurse(n):
return recurse(n - 1)
❌ No base case → infinite recursion → crash
Interview Insight
If asked:
“What are the downsides of recursion?”
Strong answers mention:
- Risk of stack overflow
- Extra memory from call stack
- Harder debugging compared to loops
How to Think Recursively (Interview Strategy)
Instead of asking:
“How do I loop through everything?”
Ask:
- What is one step of the problem?
- What is the rest of the problem?
- When should this process stop?
A helpful question:
“If this works for a smaller input, can I use it to solve the current one?”
Recursion vs Iteration
| Recursion | Iteration |
|---|---|
| Uses the call stack | Uses loops |
| Natural for hierarchical data | Often more memory efficient |
| Cleaner for trees | Less risk of stack overflow |
Rule of thumb:
- Trees & graphs → recursion
- Simple array processing → iteration
Recursion in Tree Traversal
Trees are inherently recursive:
- A tree is a node
- Each node has child subtrees
Depth-First Traversal Pattern
dfs(node):
if node is null:
return
process(node)
dfs(node.left)
dfs(node.right)
Each call:
- Handles one node
- Delegates traversal to children
This pattern shows up constantly in interviews.
Recursion in Graph Traversal
Graphs also use recursion, but require infinite cycle protection.
dfs(node):
if node is visited:
return
mark node visited
for neighbor in node.neighbors:
dfs(neighbor)
Key idea:
- Recursion explores depth
- A visited set prevents infinite loops
Quiz
Question: What does f(3) return for the pseudo-code below?
f(n):
if n == 0:
return 1
return 2 * f(n - 1)
A. 4
B. 6
C. 8
D. 16
Answer
Question: Given the pseudo-code, what is sum(4)?
sum(n):
if n == 1:
return 1
return n + sum(n - 1)
A. 4
B. 10
C. 7
D. 1
Answer
Question: How many numbers are printed by countDown(3)?
countDown(n):
if n < 0:
return
print(n)
countDown(n - 1)
A. 2
B. 3
C. 4
D. 5
Answer
Question: What is the time complexity of the pseudo-code?
h(n):
if n == 0:
return
h(n - 1)
h(n - 1)
A. O(n)
B. O(n log n)
C. O(2ⁿ)
D. O(n²)
Answer
Question: What does this dfs function compute on a binary tree?
dfs(node):
if node is null:
return 0
left = dfs(node.left)
right = dfs(node.right)
return 1 + max(left, right)
A. Number of leaves
B. Tree height
C. Sum of node values
D. Inorder traversal