Recursion
Base Case and Recursive Case
Classic Example: Factorial
The factorial of n (written n!) is n × (n-1) × (n-2) × ... × 1, with the special case that 0! is defined as 1. That definition maps directly onto a base case and a recursive case.
#include <iostream>
using namespace std;
int factorial(int n) {
if (n <= 1) {
return 1; // base case
}
return n * factorial(n - 1); // recursive case
}
int main() {
cout << factorial(5) << endl; // Output: 120
return 0;
}
// factorial(5)
// = 5 * factorial(4)
// = 5 * (4 * factorial(3))
// = 5 * (4 * (3 * factorial(2)))
// = 5 * (4 * (3 * (2 * factorial(1))))
// = 5 * 4 * 3 * 2 * 1 = 120Classic Example: Fibonacci
The Fibonacci sequence (0, 1, 1, 2, 3, 5, 8, 13, ...) is another textbook recursive definition: each number is the sum of the two before it, with the first two numbers as base cases.
#include <iostream>
using namespace std;
int fibonacci(int n) {
if (n <= 1) {
return n; // base cases: fib(0) = 0, fib(1) = 1
}
return fibonacci(n - 1) + fibonacci(n - 2); // recursive case
}
int main() {
for (int i = 0; i < 10; i++) {
cout << fibonacci(i) << " ";
}
cout << endl;
// Output: 0 1 1 2 3 5 8 13 21 34
return 0;
}Stack Overflow Risk
Every recursive call adds a new frame to the program's call stack, holding that call's local variables and return address. The call stack has a finite size.
RecursionError. If recursion goes too deep, the program exhausts its call stack and crashes with a stack overflow — and in C++, that is undefined behavior, not a well-defined exception. There is no guarantee of a clean error message; the program may simply crash, or in rare cases corrupt memory silently. Always ensure your base case is reachable and your recursion depth is bounded to something the stack can handle.// Dangerous: no base case, or a base case that's never reached.
int badCountdown(int n) {
cout << n << endl;
return badCountdown(n - 1); // never stops - eventually crashes
}
// Safe: base case guarantees termination.
int goodCountdown(int n) {
if (n <= 0) {
cout << "Liftoff!" << endl;
return 0;
}
cout << n << endl;
return goodCountdown(n - 1);
}Tail Recursion
// Tail-recursive style: the recursive call is the last thing that happens.
int factorialTail(int n, int accumulator = 1) {
if (n <= 1) {
return accumulator;
}
return factorialTail(n - 1, n * accumulator); // tail call
}When to Prefer Iteration
For performance-critical code, or recursion whose depth scales with input size (and could plausibly be large), an iterative loop is usually the safer and often faster choice — it uses constant stack space and avoids relying on an optimization the compiler may or may not perform.
// Iterative factorial: no recursion, no stack growth, no overflow risk.
int factorialIterative(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}Key Points
Every recursive function needs a reachable base case and a recursive case that progresses toward it.
Factorial and Fibonacci are classic examples of problems with a natural recursive definition.
C++ has no built-in recursion depth limit; excessive recursion causes a stack overflow, which is undefined behavior.
Tail call optimization in C++ is implementation-defined, not guaranteed by the standard.
Prefer iteration over deep recursion when performance or stack safety is a concern.