← Algorithms Primer

Recursion

~380 words Β· 2 min read

A function that calls itself

Recursion is a problem-solving technique where a function calls itself on a smaller version of the same problem. It shines when a problem has a naturally self-similar structure: trees, divide-and-conquer, mathematical definitions.

The two required parts

Every correct recursive function has two pieces:

  • Base case β€” the smallest input, answered directly without recursion. This stops the chain.
  • Recursive case β€” calls the function on a strictly smaller input, moving toward the base case.
// Factorial: n! = n Γ— (nβˆ’1)!, with 0! = 1
int factorial(int n) {
  if (n <= 1) return 1;          // base case
  return n * factorial(n - 1);   // recursive case
}

Forget the base case and the function calls itself forever β€” until the stack overflows.

The call stack

Each recursive call pushes a new frame onto the call stack. For factorial(4) the stack grows like this:

factorial(4)
  β†’ 4 Γ— factorial(3)
        β†’ 3 Γ— factorial(2)
              β†’ 2 Γ— factorial(1)
                    β†’ 1        // base case returns

Then the stack unwinds: 2Γ—1=2, 3Γ—2=6, 4Γ—6=24. Every recursive solution uses O(depth) stack space β€” which is why extremely deep recursion fails.

Fibonacci: a cautionary tale

int fib(int n) {
  if (n < 2) return n;
  return fib(n - 1) + fib(n - 2);   // two recursive calls
}

This naΓ―ve Fibonacci is O(2ⁿ) β€” it recomputes the same values exponentially many times. The fix is memoization (caching results) or rewriting as iteration, both of which make it O(n).

Tail recursion

A function is tail recursive when the recursive call is the very last thing it does β€” nothing wraps the result. Some compilers (notably in functional languages like Scheme or Haskell) can optimize this into a loop, reusing one stack frame and avoiding overflow:

// Tail-recursive factorial: accumulator carries the result
int fact(int n, int acc) {
  if (n <= 1) return acc;
  return fact(n - 1, n * acc);   // nothing wraps the call
}
Any recursive algorithm can be rewritten as a loop with an explicit stack. Recursion is a tool for clarity, not speed β€” reach for it when the problem is naturally recursive (trees, backtracking, divide-and-conquer), and prefer iteration when depth is large or performance is critical.