Section 4 · 10 min read
Recursion
How recursive functions execute on the call stack, how to trace their output and return values, how to count calls, and how to turn them into recurrences.
On this page
What a recursive function is
A function is recursive when it calls itself, directly or through other functions. Every correct recursive function has two parts:
- A base case that returns without recursing.
- A recursive case that calls the function on a smaller input, so that the base case is eventually reached.
If the input does not shrink toward the base case on every path, the recursion never ends and the program eventually overflows the stack.
How a call executes
Each call creates an activation record (stack frame) holding its parameters, local variables and the return address. Frames are pushed when a call starts and popped when it returns, so the most recent call always finishes first. Two consequences matter for tracing:
- Each active call has its own copies of parameters and automatic locals. Changing
nin one call does not affectnin the caller. - Static and global variables are shared. There is one copy, and every activation reads and writes the same storage.
The maximum number of frames alive at once is the recursion depth. Extra space used by a recursive function is proportional to its depth, not to its total number of calls.
Tracing output
The position of a printf relative to the recursive call decides the order of output.
void pre(int n) { if (n == 0) return; printf("%d ", n); pre(n - 1); } /* 3 2 1 */
void post(int n) { if (n == 0) return; post(n - 1); printf("%d ", n); } /* 1 2 3 */
- Print before the call: output appears on the way down, in calling order.
- Print after the call: output appears on the way back up, in reverse calling order.
- Print both before and after: output is a palindrome-like pattern, down then up.
When a function makes two or more recursive calls, draw the recursion tree. Each node is one call; its children are the calls it makes, left to right in program order. The output order is then a traversal of that tree: a print before the calls is a preorder visit, a print between two calls is an inorder visit, and a print after the calls is a postorder visit.
Tracing return values
For return values, work bottom-up. Compute the base cases, then build a small table until you reach the asked value. This is faster and safer than expanding the full recursion tree, and it works whenever the function has no side effects.
When there are side effects (static counters, global arrays, printing), you must follow the actual execution order, frame by frame.
Static variables inside recursion
A static local is created once. Values written on the way down are still there on the way back up. A common pattern:
int g(int n) {
static int c = 0;
int t;
if (n == 0) return c;
c += n;
t = g(n - 1);
return t + c;
}
All the additions to c happen during the descent. By the time any frame returns, c already holds its final value, and every frame adds that final value. For g(3), c ends at 6 and the result is .
If the function were written return g(n - 1) + c; the compiler could read c before or after making the call, since the evaluation order of the operands of + is unspecified. Well-posed questions store the recursive result in a local first, as above.
Counting calls
Let be the number of calls made by , counting the call itself. Write a recurrence that mirrors the code. For the naive Fibonacci function:
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
This gives and in closed form , where is the Fibonacci sequence with . Calls grow exponentially, roughly as with .
The number of additions performed is , and the number of leaves (calls that hit the base case) of fib(n) is .
From code to recurrence
To get the running time, write for the work done by a call on input :
| Code shape | Recurrence | Solution |
|---|---|---|
| one call on , constant work | ||
| one call on , linear work | ||
| two calls on | ||
| one call on | ||
| two calls on | ||
| two calls on , linear work |
A classic trap: return f(n / 2) + f(n / 2); makes two calls and costs , while return 2 * f(n / 2); makes one call and costs . The return value is the same.
Classic recursive functions
- Factorial: , depth , time .
- GCD (Euclid): , . Number of steps is .
- Fast power: , times if is odd. One recursive call per halving, so multiplications.
- Tower of Hanoi: moving disks takes moves, so . The largest disk moves exactly once; disk from the top moves times.
- Binary representation:
p(n / 2)then printn % 2prints in binary. - Digit sum:
n % 10 + f(n / 10)with depth equal to the number of digits plus one.
Tail recursion
A call is a tail call if nothing remains to be done in the caller after it returns. return f(n - 1, acc * n); is a tail call; return n * f(n - 1); is not, because the multiplication happens after the call. A compiler can turn tail recursion into a loop and reuse a single frame, which reduces space from to . GATE asks you to recognise tail calls and to know that they can be converted to iteration.
Mutual recursion
Two functions can call each other, as in A calling B and B calling A. Declare one prototype first so the compiler knows the second name. Trace mutual recursion exactly like ordinary recursion: expand one level at a time and evaluate bottom-up.
Worked examples
Worked example 1: a print between two calls
void t(int n) {
if (n == 0) return;
t(n - 1);
printf("%d ", n);
t(n - 1);
}
t(1) prints 1. t(2) prints the output of t(1), then 2, then t(1) again: 1 2 1. t(3) prints 1 2 1, then 3, then 1 2 1.
Output of t(3): 1 2 1 3 1 2 1. The number of values printed is , which is 7 for . This is the same structure as the Tower of Hanoi, and it is an inorder walk of the recursion tree.
Worked example 2: value and call count together
int f(int n) {
if (n <= 2) return n;
return f(n - 1) + f(n - 3);
}
Find f(6) and the total number of calls.
Values: , then , , , .
Calls: for and . So , , , .
Answer: f(6) returns 7 using 11 calls.
Worked example 3: a static variable across two top-level calls
int r(int n) {
static int s = 0;
if (n <= 0) return s;
s = s + n;
return r(n - 2) + n;
}
main calls r(5) twice and prints both results.
First call: descending, s becomes 5, 8, 9; r(-1) returns 9. Returning: r(1) = 9 + 1 = 10, r(3) = 10 + 3 = 13, r(5) = 13 + 5 = 18.
Second call: s starts at 9, not 0. Descending it becomes 14, 17, 18; r(-1) returns 18. Returning: .
Output: 18 and 27. The second answer is the trap: a static does not reset between top-level calls.
Worked example 4: depth of fast exponentiation
int pw(int x, int n) {
if (n == 0) return 1;
int h = pw(x, n / 2);
if (n % 2) return h * h * x;
return h * h;
}
pw(3, 5) calls pw(3, 2), which calls pw(3, 1), which calls pw(3, 0). That is 4 calls in total, all on one chain, so the depth is 4. Returning: pw(3, 0) = 1, pw(3, 1) = 1 * 1 * 3 = 3, pw(3, 2) = 3 * 3 = 9, pw(3, 5) = 9 * 9 * 3 = 243.
In general the call count is for , so the time is . Storing the half result in h is what makes this logarithmic; computing pw(x, n / 2) * pw(x, n / 2) would bring back .
Common traps
- Giving each recursive call its own copy of a static variable. There is only one.
- Forgetting that a static keeps its value between separate calls from
main. - Reading output in calling order when the print comes after the recursive call.
- Counting only the recursive calls and forgetting the first call, or the reverse. Read what the question counts.
- Confusing depth (space) with the total number of calls (time).
- Assuming
f(n/2) + f(n/2)is as cheap as2 * f(n/2). - Missing a base case path:
if (n == 0)with a step ofn - 2never terminates for oddn. - Expanding by hand when a bottom-up table would be quicker and less error-prone.
Quick revision
- Every call gets a fresh frame with its own parameters and automatic locals; statics and globals are shared.
- Print before the call gives descending order, after the call gives the reverse.
- Two calls with a print between them produce outputs.
- Calls of naive Fibonacci: with ; .
- Tower of Hanoi needs moves.
- is ; is ; is .
- Tail recursion can be turned into a loop with extra space.
- Space used by recursion equals the maximum depth times the frame size.