Recursion, or Mirrors Inside Mirrors

· 2 min read · Syed Omar Faruk Towaha
Recursion, or Mirrors Inside Mirrors

Stand between two mirrors and you see yourself, inside yourself, inside yourself, fading into green-tinted infinity. That's recursion. The only difference is that good recursion knows when to stop, while your reflection in the barber shop does not.

The definition

A recursive function solves a problem by calling itself on a smaller version of the same problem. Every recursive function needs two parts:

  1. A base case: the smallest version, answered directly.
  2. A recursive case: break the problem down and call yourself.
def factorial(n: int) -> int:
    if n <= 1:          # base case
        return 1
    return n * factorial(n - 1)   # recursive case
How the calls stack up
Each call waits for the smaller one to finish, then multiplies.

Forget the base case and you get the barber-shop mirrors: the function calls itself forever, until Python stops you with RecursionError: maximum recursion depth exceeded. It's the language's way of saying "please step out of the mirror."

Where recursion feels natural

Recursion shines when the data itself is recursive, meaning a thing that contains smaller things of the same kind:

from pathlib import Path

def total_size(path: Path) -> int:
    if path.is_file():
        return path.stat().st_size
    return sum(total_size(child) for child in path.iterdir())

Try writing that with plain loops and a manual stack. You can, but the recursive version reads like the definition: "the size of a folder is the sum of the sizes of what's inside it."

The trap: doing the same work twice

The famous bad example is Fibonacci:

def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

Beautiful. Also terribly slow, because fib(30) recalculates fib(28) and friends over and over. The fix is memoisation: remember answers you've already computed.

from functools import cache

@cache
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)
Calls, naive vs memoised
One decorator turns millions of calls into dozens.

That one line turns exponential time into linear time. It's also the doorway to dynamic programming, which is just recursion with a good memory.

When not to recurse

Python doesn't optimise deep recursion, and the default limit is around 1,000 calls. For something like "walk a linked list of a million nodes," a loop is safer. Recursion is a tool for clarity, not a personality trait.

How to think recursively

Don't try to trace every call in your head. Instead, trust the function: assume total_size(child) already works for smaller folders, and ask only "how do I combine those answers?" It's a strange leap of faith, a bit like believing your reflection will stop eventually. It always does, as long as you remember the base case.

// related

// prefer the terminal?

Open the terminal blog and type read recursion-mirrors-inside-mirrors.