Docs
README
Recursion in C++
Table of Contents
- •Introduction
- •How Recursion Works
- •Base Case and Recursive Case
- •The Call Stack
- •Classic Recursive Problems
- •Recursion vs Iteration
- •Tail Recursion
- •Common Pitfalls
- •Best Practices
Introduction
Recursion is a technique where a function calls itself to solve smaller instances of the same problem. It's a powerful tool for problems with self-similar structure.
int factorial(int n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}
When to Use Recursion
- •Tree/graph traversal
- •Divide and conquer algorithms
- •Problems with natural recursive definition (factorial, Fibonacci)
- •Backtracking (puzzles, permutations)
How Recursion Works
The Three Requirements
Every recursive function needs:
- •Base case: Condition that stops recursion
- •Recursive case: Function calls itself with smaller/simpler input
- •Progress toward base case: Each call must move closer to termination
void countdown(int n) {
if (n <= 0) { // 1. Base case
cout << "Liftoff!" << endl;
return;
}
cout << n << "..." << endl;
countdown(n - 1); // 2. Recursive case (n gets smaller = progress)
}
Visualization
countdown(3)
├── prints "3..."
└── countdown(2)
├── prints "2..."
└── countdown(1)
├── prints "1..."
└── countdown(0)
└── prints "Liftoff!" (base case)
Base Case and Recursive Case
Identifying Base Cases
Ask: "What's the simplest case that I can solve directly?"
| Problem | Base Case |
|---|---|
| Factorial | n ≤ 1 → return 1 |
| Fibonacci | n ≤ 1 → return n |
| Sum of array | empty array → return 0 |
| String length | empty string → return 0 |
| Binary search | low > high → not found |
Multiple Base Cases
Sometimes you need more than one:
int fibonacci(int n) {
if (n == 0) return 0; // Base case 1
if (n == 1) return 1; // Base case 2
return fibonacci(n-1) + fibonacci(n-2);
}
Recursive Case Design
The recursive call should:
- •Reduce problem size
- •Eventually reach base case
- •Combine results correctly
int sumArray(const int* arr, int size) {
if (size == 0) return 0; // Base
return arr[0] + sumArray(arr + 1, size - 1); // Reduce size by 1
}
The Call Stack
Each recursive call creates a new stack frame with its own:
- •Parameters
- •Local variables
- •Return address
Stack Visualization for factorial(4)
┌─────────────────────────────┐
│ factorial(1) → returns 1 │ ← Top (last called, first to return)
├─────────────────────────────┤
│ factorial(2) → returns 2×1 │
├─────────────────────────────┤
│ factorial(3) → returns 3×2 │
├─────────────────────────────┤
│ factorial(4) → returns 4×6 │ ← Bottom (first called, last to return)
└─────────────────────────────┘
Execution Flow
factorial(4) called
→ factorial(3) called
→ factorial(2) called
→ factorial(1) called
← returns 1
← returns 2 * 1 = 2
← returns 3 * 2 = 6
← returns 4 * 6 = 24
Stack Overflow
Too many recursive calls exhaust the stack:
void infinite() {
infinite(); // Stack overflow!
}
Typical stack limit: ~1MB (varies by system)
- •Approximately 10,000-100,000 recursive calls
Classic Recursive Problems
1. Factorial
long long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// factorial(5) = 5 × 4 × 3 × 2 × 1 = 120
2. Fibonacci
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
// fibonacci(6) = 8 (0, 1, 1, 2, 3, 5, 8)
3. Sum of Digits
int sumDigits(int n) {
if (n == 0) return 0;
return (n % 10) + sumDigits(n / 10);
}
// sumDigits(1234) = 1 + 2 + 3 + 4 = 10
4. Power
double power(double base, int exp) {
if (exp == 0) return 1;
if (exp < 0) return 1.0 / power(base, -exp);
return base * power(base, exp - 1);
}
5. GCD (Euclidean Algorithm)
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
// gcd(48, 18) = gcd(18, 12) = gcd(12, 6) = gcd(6, 0) = 6
6. Binary Search
int binarySearch(const vector<int>& arr, int target, int low, int high) {
if (low > high) return -1;
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] > target) return binarySearch(arr, target, low, mid - 1);
return binarySearch(arr, target, mid + 1, high);
}
7. Reverse String
string reverse(const string& s) {
if (s.length() <= 1) return s;
return reverse(s.substr(1)) + s[0];
}
// reverse("hello") = reverse("ello") + 'h'
// = reverse("llo") + 'e' + 'h'
// = ... = "olleh"
Recursion vs Iteration
Most recursive functions can be written iteratively:
Factorial Comparison
// Recursive
long long factorialRec(int n) {
if (n <= 1) return 1;
return n * factorialRec(n - 1);
}
// Iterative
long long factorialIter(int n) {
long long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
Trade-offs
| Aspect | Recursion | Iteration |
|---|---|---|
| Readability | Often cleaner for tree/divide-conquer | Better for simple loops |
| Memory | O(n) stack space | O(1) typically |
| Speed | Function call overhead | Usually faster |
| Debugging | Harder to trace | Easier |
| Stack overflow | Risk for deep recursion | No risk |
When to Choose Each
Use Recursion:
- •Tree traversal
- •Divide and conquer (merge sort, quick sort)
- •Backtracking problems
- •When recursive solution is much cleaner
Use Iteration:
- •Simple accumulation (sum, factorial)
- •Performance-critical code
- •Very deep recursion possible
Tail Recursion
A function is tail recursive if the recursive call is the last operation:
Non-Tail Recursive
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // Must multiply AFTER recursive call returns
}
Tail Recursive
int factorialTail(int n, int accumulator = 1) {
if (n <= 1) return accumulator;
return factorialTail(n - 1, n * accumulator); // Last operation is the call
}
Why It Matters
- •Tail recursive functions can be optimized by compilers
- •Stack frame can be reused (no stack growth)
- •Some compilers (with -O2) convert to iteration
Enabling Tail Call Optimization
g++ -O2 -foptimize-sibling-calls program.cpp
Common Pitfalls
1. Missing Base Case
// WRONG - infinite recursion!
int sum(int n) {
return n + sum(n - 1); // No base case
}
2. Base Case Never Reached
// WRONG - doesn't progress toward base
int broken(int n) {
if (n == 0) return 0;
return broken(n); // n never changes!
}
3. Wrong Direction
// WRONG - n grows instead of shrinking
int broken(int n) {
if (n > 100) return n;
return broken(n + 1); // Works but counterintuitive
}
4. Exponential Time Complexity
// Naive Fibonacci - O(2^n) time!
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2); // Many repeated calculations
}
// fib(5) calls fib(4) + fib(3)
// fib(4) calls fib(3) + fib(2) ← fib(3) calculated twice!
5. Stack Overflow
// For large n, this will crash
void deep(int n) {
if (n == 0) return;
deep(n - 1);
}
deep(1000000); // Stack overflow!
Best Practices
1. Always Start with Base Case
int solve(int n) {
// Handle base case(s) first
if (n <= 0) return 0;
// Then recursive case
return /* ... */;
}
2. Use Memoization for Overlapping Subproblems
#include <unordered_map>
unordered_map<int, long long> memo;
long long fibMemo(int n) {
if (n <= 1) return n;
if (memo.count(n)) return memo[n];
memo[n] = fibMemo(n-1) + fibMemo(n-2);
return memo[n];
}
3. Consider Converting to Iteration
// When depth could be large, use iteration
int sumIterative(const vector<int>& arr) {
int total = 0;
for (int n : arr) total += n;
return total;
}
4. Helper Functions with Accumulators
// Public interface
vector<int> reversed(const vector<int>& v) {
return reverseHelper(v, v.size() - 1, {});
}
// Private recursive helper
vector<int> reverseHelper(const vector<int>& v, int idx, vector<int> acc) {
if (idx < 0) return acc;
acc.push_back(v[idx]);
return reverseHelper(v, idx - 1, acc);
}
5. Test Edge Cases
- •Empty input
- •Single element
- •Maximum expected depth
- •Negative numbers (if applicable)
Quick Reference
// Basic structure
ReturnType recursiveFunc(params) {
if (/* base case */) {
return baseValue;
}
return /* combine with */ recursiveFunc(smallerParams);
}
// Common patterns
// Linear: f(n) = f(n-1) + something
// Binary: f(n) = f(n/2) + something
// Tree: f(n) = f(left) + f(right)
// Multiple: f(n) = f(n-1) + f(n-2)
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples