Introduction to Recursion
Definition
Recursion is when a function calls itself — directly or through a chain of other functions — to solve a problem by breaking it into a smaller version of the same problem.

The skeleton looks like this:
void func() {
// Some work
func(); // call myself with a smaller version of the problem
// Some work
}That’s the entire idea. What makes it work (and not loop forever) is the base case.
The three rules
Every correct recursive function obeys all three:
- The function calls itself. (Otherwise it isn’t recursion.)
- There is a base case — at least one input where the function returns without recursing.
- Each recursive call moves toward the base case. The argument shrinks, the range narrows, the tree gets smaller. Otherwise we recurse forever.
Miss any one of these and you get a stack overflow — the call stack grows until your program crashes.
// Counts down from N to 1, then stops.
void count(int N) {
if (N == 0) return; // ← base case
cout << N << " ";
count(N - 1); // ← progress toward base case
}Worked example: Factorial
Factorial of N is N · (N-1) · (N-2) · … · 1. The recursive definition is one line:
factorial(0) = 1
factorial(N) = N * factorial(N - 1)The base case (N == 0) and the recursive case fall straight out of the math.
The recursion tree
factorial(5) produces a single chain of calls — one call per level. There’s no branching because each call makes exactly one recursive call.
The green box is the base case. Each box shows what it eventually returned.
How the call stack does the bookkeeping
Every call to factorial(N) pushes a stack frame containing its local N. The frame waits — paused — until the recursive call returns. Then it multiplies and returns.
Notice the rhythm: push down, hit the base, then pop back up multiplying along the way. That’s the call stack doing your work for you.
Stack overflow — the danger
The call stack has a fixed maximum depth (a few thousand in most environments). Two things cause it to overflow:
- No base case. The function never stops calling itself.
- Wrong direction. The function recurses but the argument doesn’t shrink — for example,
factorial(N + 1)instead offactorial(N - 1).
Most languages cap recursion depth around 1,000–10,000. If you need deeper recursion, either raise the limit (Python’s sys.setrecursionlimit), rewrite iteratively with an explicit stack, or rely on tail-call optimization (only some languages have it — not Python or Java).
Recursion vs Iteration
Both can solve the same problems — the difference is mostly expressiveness and cost.
| Aspect | Iteration | Recursion |
|---|---|---|
| Control flow | A loop with a control variable | A function that calls itself |
| Termination | The loop condition becomes false | The recursive call hits the base case |
| Memory | Constant — a few variables | One stack frame per level — proportional to depth |
| Speed (in practice) | Usually faster (no call overhead) | Slower, but readable code wins for tree-shaped problems |
| Best for | Linear sweeps, simple counting | Trees, divide & conquer, backtracking, self-similar structure |
The same idea, both ways
Both work. For this problem iteration is clearly better — same complexity, less overhead. The recursive form really pays off for tree-shaped problems where the iterative version would need its own explicit stack.
Activity — try it both ways
Write a function to reverse a string, once iteratively and once recursively.
Types of Recursion
There are four shapes of direct recursion worth recognizing — and one for cross-function calls.
![]() |
|---|
| Types of Recursion |
Tail Recursion
The recursive call is the last thing the function does. Anything computed before it is already saved in the argument.
void printDown(int n) {
if (n == 0) return;
cout << n << " ";
printDown(n - 1); // ← last statement
}Some languages (Scheme, Scala, some C++ compilers) optimize this into a plain loop, keeping memory at O(1). Python and Java do not — recursive depth still costs stack frames.
Head Recursion
The recursive call happens before any other work in the function.
void printUp(int n) {
if (n == 0) return;
printUp(n - 1); // ← first statement
cout << n << " "; // happens on the way back up
}Head recursion naturally reverses the natural order — work happens during the unwinding phase. Compare printUp(3) with printDown(3):
printDown(3)prints3 2 1printUp(3)prints1 2 3
Same recursion, opposite output, just because of where the print sits.
Tree Recursion
The function calls itself more than once per invocation.
int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2); // two recursive calls
}This is what gives us the “tree” shape — each call branches into multiple children.
Notice fib(2) and fib(3) show up multiple times. That’s massive duplicated work — exponential time for what should be a linear problem. We’ll fix this on the Memoization page.
Nested Recursion
A recursive call is passed itself as an argument. Rare in practice, but the Ackermann function is a classic example.
int ackermann(int m, int n) {
if (m == 0) return n + 1;
if (n == 0) return ackermann(m - 1, 1);
return ackermann(m - 1, ackermann(m, n - 1)); // nested!
}Indirect Recursion
Two (or more) functions call each other in a cycle.
bool isEven(int n) {
if (n == 0) return true;
return isOdd(n - 1); // even(n) calls odd(n-1)
}
bool isOdd(int n) {
if (n == 0) return false;
return isEven(n - 1); // odd(n) calls even(n-1)
}Each function still has its own base case — but the recursion lives across function boundaries.
Fun fact: if you’ve seen the movie Inception, the dream-within-a-dream-within-a-dream structure is exactly tree recursion. Each level can spawn more levels, and when you wake up you “return” to the level above.
What the runtime actually does
When you call a function, the language runtime:
- Pushes a stack frame onto the call stack containing the function’s local variables, arguments, and return address.
- Jumps to the function’s first instruction.
- When the function
returns, pops the frame and resumes the caller at the return address.
Recursion is just this same mechanism applied to itself. The stack tracks every “in-progress” call. Each return collapses one level. The reason recursion is so natural for trees is that the call stack already is a tree traversal in disguise.
Got the mental model? Next: how to design a recursive solution from scratch — without ever tracing through the call stack by hand.
