Why should you care?
Recursion often looks intimidating when you first encounter it.
You see a function calling itself:
factorial(n - 1);
and the immediate question is:
How does this ever stop?
The answer is simple:
Recursion is a function solving a problem by solving a smaller version of the same problem.
Once you understand the base case, recursive case, and call stack, recursion becomes much less mysterious.
The Problem
Consider this problem:
Calculate the factorial of 5.
Mathematically:
5! = 5 × 4 × 3 × 2 × 1
We could write this using a loop.
But notice something interesting:
5! = 5 × 4!
4! = 4 × 3!
3! = 3 × 2!
2! = 2 × 1!
1! = 1
Each problem becomes a smaller version of itself.
That is exactly where recursion becomes useful.
The Concept
A recursive function has two essential parts:
Base Case
↓
Stops recursion
Recursive Case
↓
Calls the function again with a smaller or simpler problem
For factorial:
static int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
The base case is:
if (n == 0)
The recursive case is:
factorial(n - 1)
The problem becomes smaller every time.
Simple Explanation
Imagine standing in a line of people.
You want to know how many people are behind you.
Instead of counting everyone yourself, you ask the person behind you:
How many people are behind you?
They ask the next person.
That continues until the last person says:
Nobody is behind me.
Now the answer travels backward.
You
↓
Person 2
↓
Person 3
↓
Person 4
↓
Nobody
Then:
Nobody → 0
Person 4 → 1
Person 3 → 2
Person 2 → 3
You → 4
This is the basic idea of recursion.
Real-world Analogy
Imagine opening a box that contains another box.
Inside that box is another box.
And another.
Eventually, you reach the smallest box.
Large Box
↓
Box
↓
Box
↓
Smallest Box
The smallest box is the base case.
You then work backward:
Smallest Box
↓
Previous Box
↓
Previous Box
↓
Large Box
Recursion works similarly.
First, the calls go deeper.
Then the results return upward.
Code Example
Let's calculate factorial:
static int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
Now call:
int result = factorial(5);
The execution expands like this:
factorial(5)
↓
5 × factorial(4)
↓
5 × 4 × factorial(3)
↓
5 × 4 × 3 × factorial(2)
↓
5 × 4 × 3 × 2 × factorial(1)
↓
5 × 4 × 3 × 2 × 1 × factorial(0)
Now the base case is reached:
factorial(0) = 1
The calls return:
factorial(0) → 1
factorial(1) → 1
factorial(2) → 2
factorial(3) → 6
factorial(4) → 24
factorial(5) → 120
Final result:
120
What Happens to the Stack?
This is where recursion becomes much easier to understand.
Every function call creates an active execution context.
For:
factorial(5)
the stack conceptually becomes:
┌─────────────────┐
│ factorial(1) │
├─────────────────┤
│ factorial(2) │
├─────────────────┤
│ factorial(3) │
├─────────────────┤
│ factorial(4) │
├─────────────────┤
│ factorial(5) │
├─────────────────┤
│ main() │
└─────────────────┘
When factorial(0) is reached, the function returns.
Then the stack starts unwinding.
factorial(0)
↓
return 1
↓
factorial(1)
↓
return 1
↓
factorial(2)
↓
return 2
And so on.
This gives us two phases:
Going Down
↓
Recursive Calls
↓
Base Case
↓
Coming Back Up
↓
Return Values
The Most Important Rule
Every recursive function needs a way to make progress toward its base case.
For example:
factorial(n - 1)
is moving toward:
n = 0
This is good recursion.
But consider:
static void forever(int n) {
forever(n);
}
Nothing changes.
The function never approaches a base case.
Eventually:
More calls
↓
More stack usage
↓
Stack exhausted
↓
Stack overflow
So always ask:
What makes this recursive call closer to termination?
Common Mistakes
Mistake 1: Forgetting the base case
Bad:
static int factorial(int n) {
return n * factorial(n - 1);
}
There is no stopping condition.
Eventually the program will continue making calls until the stack is exhausted.
Mistake 2: Base case exists but is unreachable
Consider:
static void count(int n) {
if (n == 0) {
return;
}
count(n + 1);
}
If you start with:
count(5)
the values become:
5
6
7
8
9
...
The function moves away from 0.
The base case exists, but it is never reached.
Mistake 3: Confusing recursion with repetition
A loop and recursion can both repeat work.
But they work differently.
Loop:
Condition
↓
Repeat
↓
Condition
Recursion:
Function
↓
Function calls itself
↓
New call
↓
New call
Recursion uses the function-call mechanism and typically consumes stack space for active calls.
Mistake 4: Thinking recursion immediately returns the final answer
Consider:
return n * factorial(n - 1);
The multiplication cannot finish until:
factorial(n - 1)
returns.
So recursion first goes deeper and then calculates results while unwinding.
Recursion vs Iteration
The factorial problem can also be solved using a loop.
Recursive
static int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
Iterative
static int factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
Both produce the same result.
But they have different trade-offs.
| Recursion | Iteration | |
|---|---|---|
| Mechanism | Function calls | Loop |
| Stack usage | Usually grows with depth | Usually constant extra stack |
| Readability | Often elegant for recursive problems | Often simpler for straightforward repetition |
| Risk | Stack overflow | Usually no recursion-depth issue |
| Common use | Trees, graphs, divide and conquer | Repetitive calculations |
Recursion is not automatically better.
Use it when it makes the problem easier to express.
Where Recursion Is Actually Useful
Recursion is particularly natural for structures that contain smaller structures of the same kind.
Trees
A
/ \
B C
/ \
D E
A tree can be described recursively:
Tree
↓
Root
↓
Subtrees
↓
More subtrees
Tree traversal algorithms commonly use recursion.
Divide and Conquer
Algorithms such as merge sort use recursion.
The idea is:
Large Problem
↓
Split
/ \
Small Small
↓ ↓
Solve Solve
\ /
Combine
Instead of solving one large problem directly, the algorithm solves smaller versions of the same problem.
Backtracking
Backtracking algorithms also frequently use recursion.
For example:
Choose
↓
Explore
↓
Valid?
/ \
Yes No
↓ ↓
Continue Backtrack
This is common in problems involving:
- Permutations
- Combinations
- Maze solving
- Sudoku
- Constraint satisfaction
Advanced Notes
Recursion Has a Cost
Suppose a function recursively calls itself 1,000 times.
Conceptually:
Call 1
Call 2
Call 3
...
Call 1000
Each active call requires execution state.
Therefore, recursion depth can consume significant stack space.
This is why recursive algorithms must consider:
Time Complexity
+
Space Complexity
+
Maximum Recursion Depth
Tail Recursion
Consider:
static int count(int n) {
if (n == 0) {
return 0;
}
return count(n - 1);
}
The recursive call is the final operation.
This is called tail recursion.
Some programming languages and compilers can optimize certain tail-recursive calls so that they do not require a new stack frame for every call.
However, Java does not generally guarantee tail-call optimization.
Therefore, do not assume that tail recursion automatically avoids stack growth in Java.
Recursion Tree
Some recursive algorithms create multiple recursive calls.
Consider:
fib(n) = fib(n - 1) + fib(n - 2)
The calls form a tree:
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
Notice that the same subproblems can be calculated repeatedly.
This is why naive recursive Fibonacci has poor time complexity.
Dynamic programming can avoid this repeated work by storing previously calculated results.
The Bigger Picture
Recursion connects directly to several concepts we have already discussed.
Function
↓
Function Call
↓
Stack Frame
↓
Recursive Call
↓
Multiple Stack Frames
↓
Base Case
↓
Stack Unwinding
At a higher level:
Problem
↓
Smaller Problem
↓
Smaller Problem
↓
Base Case
↓
Build Answer
This is the mental model you should use whenever you see recursion.
How to Think About Any Recursive Problem
When solving a recursive problem, ask these three questions:
1. What is the smallest possible problem?
That is usually your base case.
2. How can I express the current problem using a smaller problem?
That gives you the recursive case.
3. Does every recursive call move toward the base case?
If not, the recursion is incorrect.
For example:
Factorial(n)
Base case:
factorial(0) = 1
Recursive case:
factorial(n) = n × factorial(n - 1)
Progress:
n → n - 1 → n - 2 → ... → 0
This simple pattern works for a surprisingly large number of recursive problems.
Summary
Recursion is a technique where a function solves a problem by calling itself on a smaller version of that problem.
Every recursive solution should have:
Base Case
+
Recursive Case
+
Progress Toward Base Case
The execution generally looks like:
Recursive Calls
↓
Base Case
↓
Stack Unwinding
↓
Final Result
Remember:
- Recursion uses function calls.
- Active recursive calls consume stack space.
- The base case stops recursion.
- Each call should move toward the base case.
- Recursive solutions are especially useful for trees, graphs, divide-and-conquer algorithms, and backtracking.
- Recursion is not always better than iteration.
- Poor recursion can cause stack overflow or unnecessary computation.
The most important mental model is:
Don't ask:
"How does the function solve the entire problem?"
Ask:
"How does the function solve one smaller version of the problem?"
Once you can answer that question, recursion becomes much less mysterious.
This article was originally published by DEV Community and written by Shankar L.
Read original article on DEV Community