Technology Aug 26, 2026 · 7 min read

Recursion Demystified

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 smal...

DE
DEV Community
by Shankar L
Recursion Demystified

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.

DE
Source

This article was originally published by DEV Community and written by Shankar L.

Read original article on DEV Community
Back to Discover

Reading List