Welcome back. In the previous lesson, you learned to identify a recursive function’s purpose, base case, recursive case, and the argument change that guarantees termination. Now we make that structure visible during execution.
For an exam trace, you must show more than “the function calls itself.” A complete hand trace records:
- the call order as new function calls begin;
- the exact point at which the base case stops further calls;
- the return order, as waiting calls resume;
- the final value received by the original call.
This lesson completes the first module. It focuses on single-branch recursion, where each non-base call makes one recursive call.
Recursion has a descent and an unwind
Consider the familiar factorial function:
int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
Suppose the program evaluates:
int answer = factorial(4);
At first glance, return n * factorial(n - 1); may look like one calculation. It is not. The multiplication cannot be completed until the nested call to factorial(n - 1) has produced a value.
For factorial(4), the call is temporarily waiting to evaluate:
Then factorial(3) waits to evaluate:
Each call has its own parameter value. The first call’s n remains ; calling factorial(3) does not change that stored value. It creates a new function invocation with a separate n equal to .
A useful mental model is a stack of unfinished work:
| Current call | What it must still do after the recursive call returns |
|---|---|
factorial(4) | Multiply the returned value by 4 |
factorial(3) | Multiply the returned value by 3 |
factorial(2) | Multiply the returned value by 2 |
factorial(1) | Multiply the returned value by 1 |
factorial(0) | Return 1 immediately |
The calls are made in one direction, toward the base case. Once the base case returns, the waiting operations are completed in the reverse order. This reverse completion is called unwinding.
Watch the following demonstration before doing the written trace. It models the exact level of detail worth using in an exam solution.
“Tracing a recursive function” by HurrayBanana demonstrates a complete hand trace of fact(4), including the separate parameter value in every call and the calculations performed while returning.
Watch the first frame to see how the initial call and its local n value are recorded. Continue with the call descent, focusing on why each multiplication must wait. Then watch the base case and the return phase. Notice that the trace does not guess the final answer early; it resolves each suspended expression only when its recursive call returns.
A reliable exam method for hand tracing
Use the same five-stage method whenever you trace a simple recursive function that returns a value.
1. Start with the exact initial call
Write exactly what the program invokes:
factorial(4)
Do not start with a general statement like “n decreases.” A trace follows one particular execution, so values matter.
2. Execute one function call until it pauses or ends
For factorial(4), substitute the current parameter value into the condition:
if (4 == 0)
This condition is false. The function reaches:
return 4 * factorial(3);
At this point, record that factorial(4) is waiting for factorial(3).
3. Repeat for each recursive call
Trace the new call with its new local parameter value. Continue until the base case condition is true.
For factorial, the calls occur in this order:
factorial(4)factorial(3)factorial(2)factorial(1)factorial(0)
The fifth call reaches the base case. It returns 1 and makes no further recursive call.
4. Resolve the suspended expressions in reverse order
Now substitute the returned value into the call that was waiting for it. The call that was created last finishes first.
For factorial, factorial(0) returns 1, so factorial(1) can finally calculate its multiplication. Once that returns, factorial(2) can calculate, and so on.
5. State the original call’s final value
Your trace is complete only when factorial(4) has returned its value to the statement that called it:
int answer = factorial(4); // answer becomes 24
A compact rule to remember is:
Call downward until a base case returns; then calculate upward through the waiting calls.
A complete trace of factorial(4)
Here is an exam-ready trace. The left column shows calls beginning; the right column shows the same calls finishing.
Call phase: record what each invocation is waiting to do
| Call | Base case test | Result at this moment |
|---|---|---|
factorial(4) | 4 == 0 is false | waits for 4 * factorial(3) |
factorial(3) | 3 == 0 is false | waits for 3 * factorial(2) |
factorial(2) | 2 == 0 is false | waits for 2 * factorial(1) |
factorial(1) | 1 == 0 is false | waits for 1 * factorial(0) |
factorial(0) | 0 == 0 is true | returns 1 |
The base case is the turning point. Before it, calls are being created. After it, calls begin returning.
Return phase: substitute values and calculate
| Returning call | Calculation | Returned value |
|---|---|---|
factorial(0) | Base case | 1 |
factorial(1) | 1 * factorial(0) becomes 1 * 1 | 1 |
factorial(2) | 2 * factorial(1) becomes 2 * 1 | 2 |
factorial(3) | 3 * factorial(2) becomes 3 * 2 | 6 |
factorial(4) | 4 * factorial(3) becomes 4 * 6 | 24 |
Therefore:
The initial function call returns 24, and the variable answer receives that value.
The call order was from factorial(4) down to factorial(0). The return order was from factorial(0) back up to factorial(4). They are reverse orders because each call must finish the more recent nested call before it can finish itself.

The Recursive factorial call trace uses a slightly different valid base case:
if (n > 1)
return n * factorial(n - 1);
else
return 1;
Here, the base case activates at n = 1 rather than n = 0. Consequently, the descent stops at factorial(1), and the return phase begins there. The tracing method is unchanged:
- write each call with its actual argument;
- identify the waiting expression;
- find the base case;
- resolve waiting expressions from the innermost call outward.
In an exam, do not assume the base case is always n == 0. Trace the condition in the given code.
Why printing order can differ from return order
So far, factorial has returned values. Recursive functions can also print output. When tracing output, the critical question is:
Is the
coutstatement before or after the recursive call?
Consider:
void mirror(int n) {
if (n == 0) {
return;
}
cout << n << ' ';
mirror(n - 1);
cout << n << ' ';
}
For the call mirror(3), the call order is:
mirror(3)mirror(2)mirror(1)mirror(0), which is the base case
But the printed output is:
3 2 1 1 2 3
The first cout runs during the descent, before the recursive call:
cout << n << ' ';
mirror(n - 1);
So it prints:
3 2 1
The second cout runs during unwinding, after the recursive call has returned:
mirror(n - 1);
cout << n << ' ';
So it prints:
1 2 3
This distinction is important:
| Type of statement | When it happens |
|---|---|
| Statements before the recursive call | During the call phase |
| The base-case action | At the deepest call |
| Statements after the recursive call | During the return phase |
| A returned arithmetic expression | Resolved during the return phase |
For a void function such as mirror, there is no final numerical return value. The final result is its observable output. For a non-void function such as factorial, the important result is the value returned to the original caller.
Common tracing errors
Treating n as one changing variable
A common incorrect trace says that one n changes from to , then to . In reality, every call has a separate parameter:
- the first active call has
n = 4; - inside it, a second call has
n = 3; - inside that, another has
n = 2.
When factorial(3) returns, execution resumes in the original call where n is still 4.
Calculating before the nested call has returned
This is invalid:
factorial(4) = 4 * factorial(3) = 24
It skips the reasoning needed to know factorial(3). A correct trace must first establish:
Only then can it compute:
Forgetting the base-case return value
The base case does not merely “stop” recursion. It supplies the first concrete value that allows all waiting calls to finish. In factorial:
if (n == 0) {
return 1;
}
The 1 is essential. Without it, the multiplication chain would have no value to use.
Tracing output as if it were a return value
cout displays a value immediately when execution reaches that line. return ends the current call and sends a value back to its caller. Keep these actions separate in your trace.
A compact format for timed exam questions
For simple integer recursion, this two-part layout is usually enough:
Call phase:
f(3) waits for ...
f(2) waits for ...
f(1) waits for ...
f(0) returns ...
Return phase:
f(1) = ...
f(2) = ...
f(3) = ...
For a function with output, write each printed item in the order its cout statement executes. For a function that returns a value, write the expression each call is waiting to complete, then resolve it in reverse order.
Before moving on, make sure your trace always answers these four questions:
- What is the first call?
- Which calls occur before the base case?
- What exactly does the base case return or print?
- How does each waiting call use the returned result?
Key takeaways
A hand trace of recursion has two distinct phases:
- Descent: each non-base call creates a new invocation and waits for a recursive result.
- Base case: the deepest call stops recursion and provides a direct result.
- Unwinding: calls resume in reverse order, completing their pending calculations or post-recursion statements.
- Final result: the original call returns its value to the code that invoked it.
For an exam, write the actual argument for every call, preserve each call’s separate local parameter value, and never perform an outer calculation until its inner recursive call has returned.
Next, you will move from tracing existing functions to deriving a recursive specification for scalar integer problems: identifying the smaller input, choosing a base-case result, and expressing how the current call uses the recursive result.
Can't find a good explanation? Sign up and we'll make it for you
Sign up