Topzee logo
Open menu
DSA Interview Prep

Understanding Recursion: How I Finally Understood It

A personal, practical breakdown of recursion for developers who've struggled to get it to click.

Topzee3 min read
Abstract recursion cover showing nested frames and repeated shapes in Topzee interview prep colors.

Understanding recursion can feel strange because the function calls itself before the first call is finished. For a lot of developers, the code looks short but the mental model feels slippery. The trick is to stop thinking of recursion as magic and start thinking of it as a stack of unfinished tasks.

Once that clicked for me, recursion became less scary.

Understanding Recursion: The Aha Moment

I used to read recursive functions from top to bottom like normal sequential code, then get confused when the answer seemed to appear from nowhere. The shift came when I started tracing each call as a paused frame waiting for a smaller answer.

That mental model changed the whole topic. Instead of asking “how does the function already know the answer?” I started asking “what smaller version of the problem is this call waiting for?” Once you can answer that, recursion becomes easier to debug.

The Two Parts of Recursion

Every recursive function needs two pieces:

  1. A base case
  2. A recursive case

The base case stops the function. The recursive case moves the problem closer to the base case.

Here is factorial:

function factorial(n: number): number {
  if (n <= 1) {
    return 1;
  }

  return n * factorial(n - 1);
}

factorial(5) means:

5 * factorial(4)
5 * 4 * factorial(3)
5 * 4 * 3 * factorial(2)
5 * 4 * 3 * 2 * factorial(1)

At factorial(1), the function returns 1. Then the paused calls resolve backward.

Tracing the Call Stack

The call stack is the list of active function calls waiting to finish.

For factorial(4), the stack builds like this:

factorial(4)
factorial(3)
factorial(2)
factorial(1)

Then it unwinds:

factorial(1) returns 1
factorial(2) returns 2 * 1 = 2
factorial(3) returns 3 * 2 = 6
factorial(4) returns 4 * 6 = 24

That is recursion: reduce the problem, hit the base case, then combine the answers on the way back.

The Base Case Mistake

Forgetting the base case causes infinite recursion until the runtime stops you.

function brokenCountdown(n: number): void {
  console.log(n);
  brokenCountdown(n - 1);
}

This never stops. A safe version includes a base case:

function countdown(n: number): void {
  if (n < 0) {
    return;
  }

  console.log(n);
  countdown(n - 1);
}

The base case is not optional. It is the exit door.

Recursion vs Iteration

Many recursive solutions can be written iteratively.

function factorialIterative(n: number): number {
  let result = 1;

  for (let value = 2; value <= n; value += 1) {
    result *= value;
  }

  return result;
}

The iterative version uses a loop and a variable. The recursive version uses the call stack.

Use recursion when the problem naturally breaks into smaller versions of itself:

  • Trees
  • Graph traversal
  • Backtracking
  • Divide and conquer
  • Nested structures

Use iteration when a simple loop is clearer and avoids stack depth issues.

A Better Mental Model

Do not ask, “How does the whole recursion finish?” immediately.

Ask:

  1. What is the smallest input I can answer directly?
  2. How do I reduce the current input toward that smallest input?
  3. How do I combine the smaller answer with the current step?

If you can answer those three, the recursive shape becomes easier to write.

If recursion still feels foggy, practice by tracing with pen and paper. The goal is not speed first. The goal is learning to trust each smaller call.