6.3 Recursion: Base Cases, Tracing, Errors, and Iterative Equivalents
Key Takeaways
- A correct recursive procedure has at least one base case that returns without recursing and a recursive call that moves the input toward a base case.
- Each call gets its own stack frame with its own parameters and local variables; missing or unreachable base cases cause infinite recursion and a stack overflow.
- To trace recursion, list the calls down to the base case, then substitute return values back up in reverse order.
- Naive recursive Fibonacci makes an exponential number of calls because it recomputes the same subproblems; fib(4) alone makes 9 calls.
- Every recursive algorithm has an iterative equivalent; simple linear recursions such as factorial become a loop with an accumulator.
What this competency asks
ETS asks you to understand simple recursive algorithms (for example, n factorial and the sum of the first n integers):
- Trace simple recursive algorithms.
- Provide missing steps in incomplete simple recursive algorithms.
- Identify parts of a recursive algorithm, such as the base (stopping) condition and the recursive call.
- Identify errors in simple recursive algorithms.
- Identify an iterative algorithm that is equivalent to a recursive algorithm.
The discussion questions also ask you to analyze the number of recursive calls.
Anatomy of a recursive procedure
A recursive procedure calls itself on a smaller version of the same problem.
int factorial ( int n )
if ( n ≤ 1 )
return 1 // base case
else
return n * factorial ( n - 1 ) // recursive call on a smaller input
end if
end factorial
| Part | Role | In factorial |
|---|---|---|
| Base case (stopping condition) | Answers the smallest input directly, without recursing | n ≤ 1 returns 1 |
| Recursive call | Solves a smaller instance | factorial ( n - 1 ) |
| Progress toward the base case | Guarantees the calls eventually stop | n decreases by 1 each time |
| Combining step | Builds the answer from the smaller result | n * … |
Tracing recursion: down, then up
Each call waits for the call it makes. Write the calls going down, then fill in return values coming back up.
factorial ( 4 )
factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 ← base case
factorial(2) = 2 * 1 = 2
factorial(3) = 3 * 2 = 6
factorial(4) = 4 * 6 = 24
Four calls are active at the deepest point, one stack frame per call. Each frame holds its own copy of n. When a call returns, its frame is removed and the caller resumes where it left off.
Sum of the first n integers
int sumTo ( int n )
if ( n == 0 )
return 0
else
return n + sumTo ( n - 1 )
end if
end sumTo
sumTo(4) = 4 + sumTo(3) = 4 + 3 + sumTo(2) = 4 + 3 + 2 + sumTo(1) = 4 + 3 + 2 + 1 + sumTo(0) = 4 + 3 + 2 + 1 + 0 = 10. That takes 5 calls, for n = 4, 3, 2, 1, and 0.
Supplying a missing step
To fill in a missing recursive step, write the relationship between the answer for n and the answer for a smaller input. For example, suppose a procedure should return the product of the integers from 3 through n (for n ≥ 3):
int prodFrom3 ( int n )
if ( n == 3 )
return 3
else
/* missing statement */
end if
end prodFrom3
The product from 3 to n equals n times the product from 3 to n − 1, so the missing statement is return n * prodFrom3 ( n - 1 ). Check it with a small case: prodFrom3(5) = 5 × prodFrom3(4) = 5 × 4 × prodFrom3(3) = 5 × 4 × 3 = 60. Wrong choices typically recurse on the same n, which never ends, or jump too far, such as n - 3, which skips the base case.
Finding errors
| Error | Example | Effect |
|---|---|---|
| Missing base case | return n + sumTo ( n - 1 ) with no if | Infinite recursion, then a stack overflow |
| Base case never reached | Calling factorial ( n + 1 ), or testing n == 0 when n can be negative or can skip 0 | Stack overflow |
| Wrong base value | return 0 as the base case of factorial | Every result becomes 0 |
| Wrong combining step | return n * sumTo ( n - 1 ) in a sum | Computes a product (and returns 0, from the base case) |
| No progress | return f ( n ) | Calls itself with the same input forever |
Counting calls: when recursion branches
int fib ( int n )
if ( n ≤ 1 )
return n
else
return fib ( n - 1 ) + fib ( n - 2 )
end if
end fib
Each non-base call makes two calls, so the calls form a tree:
fib(4)
/ \
fib(3) fib(2)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
Counting nodes gives 9 calls for fib(4). fib(2) is computed twice and fib(1) three times. The number of calls grows exponentially with n, roughly O(2ⁿ). Storing results that have already been computed (memoization, O(n) extra memory) or building up from fib(0) with a loop reduces the time to O(n).
Recursion and iteration
Any recursive algorithm can be rewritten iteratively. Simple linear recursions translate directly into a loop with an accumulator:
int factorialLoop ( int n )
int result ← 1
for ( int i ← 2; i ≤ n; i ← i + 1 )
result ← result * i
end for
return result
end factorialLoop
To check that an iterative version is equivalent, compare them on the base case, for example n = 0 or 1 (both should return 1), and on a small value such as 4 (both should return 24).
| Recursive | Iterative | |
|---|---|---|
| Memory | One stack frame per active call: O(depth) | Usually O(1) extra |
| Failure mode | Stack overflow when too deep | Infinite loop |
| Natural fit | Trees, divide-and-conquer, nested structures | Simple counting and accumulation |
Some languages, such as Scheme, guarantee tail-call optimization. When the recursive call is the very last action, the language reuses the current frame, so deep tail recursion does not overflow the stack. Many popular languages, including Java and Python, do not perform this optimization.
A recursive algorithm you already know
Binary search (Section 6.1) is naturally recursive. Search the middle, then call the same procedure on the left or right half, with low > high as the base case for "not found." Because it makes only one recursive call per level, it makes about log₂ n calls, not an exponential number.
Consider this procedure, intended to return the sum 1 + 2 + … + n.
What happens when int total ( int n )
return n + total ( n - 1 )
end total
total ( 5 ) is called?
What value is returned by mystery ( 4, 5 )?
int mystery ( int a, int b )
if ( b == 0 )
return 0
else
if ( b % 2 == 0 )
return mystery ( a + a, b / 2 )
else
return a + mystery ( a, b - 1 )
end if
end if
end mystery
The procedure count ( n ) should return 1 + 2 + … + n for n ≥ 1. It begins if ( n == 1 ) return 1 else /* missing statement */ end if. Which statement correctly replaces /* missing statement */?
Using the recursive fib procedure that returns n when n ≤ 1 and otherwise returns fib(n − 1) + fib(n − 2), how many total calls to fib, including the first, are made when evaluating fib(4)?