The C Path — learn C, visually

📈 Algorithms & Complexity

Recursion: functions that call themselves

⏱ 14 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

You met the stack-overflow crash on Part 0's memory map — this lesson teaches you to cause one (on purpose, once) and then avoid it forever, because it's exactly what happens to a function that calls itself with no brake.

Handled right, a self-calling function solves in three elegant lines what loops turn into a nightmare — it's the engine inside the quicksort and merge sort you just watched, and today you'll see exactly how it works.

A recursive function solves a problem by calling itself on a smaller version of the same problem. It sounds like cheating — "to sort the array, sort the array" — but you already watched it work: quicksort and merge sort are recursion. The trick is that each call shrinks the job, until it's so small the answer is obvious.

The anatomy: base case + recursive case

Every correct recursive function has exactly two ingredients:

fact.c
#include <stdio.h>

long fact(int n) {
    if (n <= 1)                 /* BASE case: brake!     */
        return 1;
    return n * fact(n - 1);     /* RECURSIVE case:       */
}                               /*   shrink toward base  */

int main(void) {
    printf("4!  = %ld\n", fact(4));
    printf("10! = %ld\n", fact(10));
    return 0;
}
terminal
$ gcc fact.c -o fact && ./fact
4!  = 24
10! = 3628800
💀

No base case, no mercy: long bad(int n) { return n + bad(n - 1); } never stops shrinking past zero. Each call adds a stack frame; the stack is finite (typically ~8 MB, roughly 10⁵–10⁶ frames), so you get a stack overflow — on Linux, a segfault. Same fate if the base case exists but the input doesn't move toward it.

Watch the call stack breathe

Recursion works because of something you met in Part 0: every call gets its own stack frame with its own n. Four calls to fact means four separate ns alive at once. Step through and watch the stack grow, hit the base case, then unwind while multiplying:

This spot has an interactive trace widget — open the interactive lesson to play with it.

🧠 Checkpoint: During fact(4), how many separate variables named n exist at the deepest moment?

  • 1 — it is overwritten each call
  • 4 — one per active stack frame
  • 2 — caller and callee
  • 0 — n lives in a register
Show answer

4 — one per active stack frame — Each call pushes a fresh frame with its own n (4, 3, 2, 1 — all alive at once). That per-call privacy is the entire mechanism that makes recursion work.

Fibonacci: recursion's cautionary tale

The Fibonacci definition — fib(n) = fib(n−1) + fib(n−2) — translates to gorgeous, tragic C:

fib_naive.c
#include <stdio.h>

long long fib(int n) {          /* naive: TWO calls per call */
    if (n < 2) return n;        /* fib(0)=0, fib(1)=1        */
    return fib(n - 1) + fib(n - 2);
}

int main(void) {
    printf("fib(45) = %lld\n", fib(45));
    return 0;
}
terminal
$ gcc -O2 fib_naive.c -o fib_naive && time ./fib_naive
fib(45) = 1134903170

real    0m4.31s
# ~4 seconds — and every +1 to n multiplies the time by ~1.6

Four seconds?! For one number?! Look at what the calls actually do:

the call tree of fib(5)
fib(5)
├── fib(4)
│   ├── fib(3)
│   │   ├── fib(2)   ← computed here…
│   │   └── fib(1)
│   └── fib(2)       ← …and again here…
└── fib(3)           ← this WHOLE subtree is a repeat!
    ├── fib(2)       ← …and again here
    └── fib(1)

15 calls for fib(5). fib(45) makes ~3.6 BILLION calls.

Every call spawns two more, and the same subproblems get recomputed again and again — fib(2) alone is computed 3 times in that tiny tree, and about a billion times in fib(45). The call count roughly doubles per level: exponential, about Θ(1.618ⁿ) (the golden ratio!), which the O(2ⁿ) curve bounds from above:

This spot has an interactive bigo widget — open the interactive lesson to play with it.

🧠 Checkpoint: Why is naive recursive fibonacci exponential?

  • Function calls are inherently slow in C
  • The same subproblems are recomputed an exponential number of times
  • long long arithmetic is O(n)
  • The stack gets too deep
Show answer

The same subproblems are recomputed an exponential number of times — Each call branches into two, and the branches endlessly repeat work already done elsewhere in the tree — fib(2) is recomputed ~a billion times inside fib(45). Depth is only n; the WIDTH of the tree is the killer.

The fix: remember what you computed

Memoization — cache each answer the first time, return the cached value ever after. One array turns an exponential algorithm into a linear one:

fib_memo.c
#include <stdio.h>

long long memo[93];   /* fib(92) is the last to fit long long */

long long fib(int n) {
    if (n < 2) return n;
    if (memo[n]) return memo[n];       /* cached? done: O(1) */
    return memo[n] = fib(n - 1) + fib(n - 2);
}

int main(void) {
    printf("fib(45) = %lld\n", fib(45));
    printf("fib(90) = %lld\n", fib(90));
    return 0;
}
terminal
$ gcc -O2 fib_memo.c -o fib_memo && time ./fib_memo
fib(45) = 1134903170
fib(90) = 2880067194370816120

real    0m0.002s
# each fib(k) computed once, then looked up: Θ(n). 2000x faster,
# and it reaches n=90 where the naive version needs ~centuries.

Recursion vs iteration

Anything recursive can be rewritten with a loop (plus, sometimes, an explicit stack — next lesson!), and vice versa. Rules of thumb:

🧠 Checkpoint: What is the complexity of MEMOIZED fibonacci?

  • Θ(n)
  • Θ(2ⁿ) still
  • Θ(n log n)
  • Θ(1)
Show answer

Θ(n) — Each value fib(0..n) is computed exactly once (constant work each) and every repeat hits the O(1) cache check. n distinct computations → Θ(n). Trading O(n) memory for exponential time is the best deal in computing.

The showpiece: Tower of Hanoi

Move n disks from peg A to peg C, one at a time, never placing a bigger disk on a smaller one. Iteratively: a nightmare. Recursively: three lines of pure elegance — move n−1 disks out of the way, move the big one, move the n−1 back on top.

hanoi.c
#include <stdio.h>

void hanoi(int n, char from, char to, char via) {
    if (n == 0) return;              /* base: nothing to move */
    hanoi(n - 1, from, via, to);     /* clear the n-1 above   */
    printf("move disk %d: %c -> %c\n", n, from, to);
    hanoi(n - 1, via, to, from);     /* stack them back on    */
}

int main(void) {
    hanoi(3, 'A', 'C', 'B');
    return 0;
}
terminal
$ gcc hanoi.c -o hanoi && ./hanoi
move disk 1: A -> C
move disk 2: A -> B
move disk 1: C -> B
move disk 3: A -> C
move disk 1: B -> A
move disk 2: B -> C
move disk 1: A -> C

🤔 hanoi(3) printed 7 moves. How many moves for n disks — and is a faster algorithm possible?

Think first

Moves(n) = 2 × Moves(n−1) + 1, which solves to 2ⁿ − 1. And no algorithm can beat it — the biggest disk can only move when all n−1 others are parked on the spare peg, which provably requires this many steps. Here the problem itself is Θ(2ⁿ); the legend where monks move 64 disks would take 2⁶⁴−1 moves ≈ 585 billion years at one per second.

This spot has an interactive editor widget — open the interactive lesson to play with it.

To trace recursion we kept talking about "the stack" — pushing frames, popping frames. That push/pop discipline is a data structure in its own right, and it's next.

▶ Practice this lesson interactively (with live gcc)