Students often think recursion is a special language feature. It isn't. Every recursive call is an ordinary function call — which means a stack frame gets pushed, parameters get copied, a return address gets saved, and the frame gets popped on return. The only thing that makes recursion different is that the callee happens to be the same function as the caller. That's it.
What lives in a stack frame
When factorial(4) calls factorial(3), the CPU pushes a frame
for the new call. That frame contains:
- The return address — where to jump when this call returns
- The saved frame pointer — so the caller's frame can be restored
- Parameters — a copy of the argument
n - Local variables — anything declared inside the function
On x86-64 Linux (the System V ABI), the first six integer arguments go in registers
(rdi, rsi, …). Only if there are more than six, or if the
compiler needs to spill them, do they land on the stack. A simple function like factorial
may have a very small frame — but it still has one. Four levels of factorial
means four live frames on the stack simultaneously.
Notice the multiplication n * factorial(n-1) cannot execute until
factorial(n-1) returns. The frame for factorial(4) must
stay alive — holding its own n and its own return address — while all
three inner calls run. The stack depth equals the recursion depth.
Linear vs tree recursion
Factorial is linear recursion: each call makes exactly one recursive call. The stack depth is O(n) and the total work is O(n). Naive Fibonacci is tree recursion: each call makes two recursive calls.
The call tree has O(2ⁿ) nodes. fib(50) makes over a trillion calls.
Stack depth stays at O(n) — only the deepest branch is live at any moment — but
the total work is exponential. This is not a recursion problem; it's a redundant
computation problem. Fix it with memoization or by rewriting iteratively.
Tail recursion
A function is tail-recursive when the recursive call is the very last operation — there's nothing left to do after it returns. The current frame can be reused instead of keeping it alive, turning the recursion into a loop at the machine level.
-O2, eliminating
the stack growth. But the C standard doesn't require it — you can't rely on it.
If depth matters, convert to an explicit loop or use an explicit stack data structure.
Converting recursion to iteration
Any recursive function can be rewritten with an explicit stack. The iterative version avoids function-call overhead and lets you control the stack size precisely. For tree traversal or DFS, this looks like:
Recursion is just function calls — the call stack is the implicit data structure, and understanding its depth and cost is how you know when to recurse and when to iterate.