C++ Recursion Advanced Programming
Algorithm Design

C++ Recursion: Complete Guide with Examples

Master C++ recursion: understand recursion vs iteration, recursive algorithms, stack management, optimization techniques, and when to use recursion effectively.

Recursion Basics

Base Case & Recursive Case

vs Iteration

Comparison & Trade-offs

Recursion Stack

Stack Frames & Memory

Optimization

Tail Recursion & Memoization

Introduction to Recursion

Recursion is a programming technique where a function calls itself to solve a problem. It breaks down complex problems into simpler, similar subproblems until a base case is reached.

Why Use Recursion?
  • Elegant solution for certain problems
  • Natural fit for tree/graph traversal
  • Simplifies divide-and-conquer algorithms
  • Easier to understand for some problems
  • Useful for backtracking problems
  • Natural for mathematical definitions
Recursion Components
  • Base Case: Stopping condition
  • Recursive Case: Calls itself
  • Recursive Call: Function calling itself
  • Stack Frames: Memory for each call
  • Return Values: Propagate back up
Recursive Function Execution Flow
1
Initial Call: Function called with initial parameters
2
Check Base Case: Test if problem is simple enough
3
Recursive Case: Break problem, call function with simpler parameters
4
Stack Build-up: Each call creates new stack frame
5
Base Case Reached: Return simple solution
6
Stack Unwinding: Combine results back up the stack

Iteration vs Recursion: Comprehensive Comparison

The following table provides a detailed comparison between iteration and recursion in C++:

Aspect Iteration (Loops) Recursion (Self-calling)
Definition Repeats block of code using loops (for, while, do-while) Function calls itself to solve smaller instances of same problem
Basic Structure for(int i=0; i
while(condition) { }
type func(params) {
  if(baseCase) return value;
  return func(modifiedParams);
}
Memory Usage Low memory
Uses constant stack space
High memory
Each call creates stack frame
Risk of stack overflow
Performance Generally faster
No function call overhead
Better cache locality
Slower
Function call overhead
Stack manipulation costs
Code Readability Simple for linear problems
Easy to debug
Straightforward flow
Elegant for certain problems
Matches problem definition
Can be harder to understand
When to Use • Simple repetition
• Sequential processing
• Performance-critical code
• Known number of iterations
• Tree/graph traversal
• Divide and conquer
• Backtracking problems
• Mathematical definitions
Termination Loop condition becomes false
Explicit break statement
Base case reached
Must be carefully defined
Debugging Easier to debug
Linear execution flow
Can use loop variables
Harder to debug
Multiple stack frames
Complex call hierarchy
Examples • Array processing
• Counting/summing
• Searching arrays
• Matrix operations
• Factorial calculation
• Fibonacci sequence
• Tree traversal
• Tower of Hanoi
Risk Factors • Infinite loops
• Off-by-one errors
• Incorrect loop conditions
• Stack overflow
• Infinite recursion
• Missing base case
• Exponential time complexity

Key Insight: Iteration vs Recursion

Any recursive algorithm can be converted to iteration (using stacks), and most iterative algorithms can be converted to recursion. The choice depends on problem nature, readability, and performance requirements.

1. Basic Recursion Examples: Factorial & Fibonacci

These classic examples demonstrate the fundamental pattern of recursion: breaking down problems into smaller subproblems.

Recursive Pattern Template
return_type recursiveFunction(parameters) {
    // 1. BASE CASE (stopping condition)
    if (simple_case_condition) {
        return base_value;
    }
    
    // 2. RECURSIVE CASE (break down problem)
    // Modify parameters to make problem smaller
    return combine(recursiveFunction(smaller_problem), current_value);
}
Factorial: Iteration vs Recursion Comparison
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;

// Function prototypes
long long factorialIterative(int n);
long long factorialRecursive(int n);
void comparePerformance(int n);

int main() {
    cout << "=== FACTORIAL: ITERATION vs RECURSION ===" << endl << endl;
    
    // Calculate factorial using both methods
    int n = 10;
    
    cout << "Calculating " << n << "! (10 factorial)" << endl;
    cout << "Iterative: " << n << "! = " << factorialIterative(n) << endl;
    cout << "Recursive: " << n << "! = " << factorialRecursive(n) << endl;
    
    cout << endl << "=======================================" << endl << endl;
    
    // Test multiple values
    cout << "Factorials from 0 to 10:" << endl;
    cout << "n\tIterative\tRecursive" << endl;
    cout << "-\t---------\t---------" << endl;
    
    for (int i = 0; i <= 10; i++) {
        cout << i << "\t" << factorialIterative(i) 
             << "\t\t" << factorialRecursive(i) << endl;
    }
    
    cout << endl << "=======================================" << endl << endl;
    
    // Performance comparison
    cout << "Performance Comparison (n=20):" << endl;
    comparePerformance(20);
    
    // Error case: Negative number
    cout << endl << "Error Handling Test:" << endl;
    try {
        cout << "Factorial of -5 (iterative): ";
        cout << factorialIterative(-5) << endl;
    } catch (const invalid_argument& e) {
        cout << "Error: " << e.what() << endl;
    }
    
    return 0;
}

// ITERATIVE FACTORIAL
long long factorialIterative(int n) {
    if (n < 0) {
        throw invalid_argument("Factorial not defined for negative numbers");
    }
    
    long long result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// RECURSIVE FACTORIAL
long long factorialRecursive(int n) {
    // Base case: 0! = 1 and 1! = 1
    if (n < 0) {
        throw invalid_argument("Factorial not defined for negative numbers");
    }
    if (n <= 1) {
        return 1;
    }
    
    // Recursive case: n! = n * (n-1)!
    return n * factorialRecursive(n - 1);
}

// Performance comparison function
void comparePerformance(int n) {
    // Test iterative version
    auto start = high_resolution_clock::now();
    long long iterResult = factorialIterative(n);
    auto stop = high_resolution_clock::now();
    auto iterDuration = duration_cast<microseconds>(stop - start);
    
    // Test recursive version
    start = high_resolution_clock::now();
    long long recurResult = factorialRecursive(n);
    stop = high_resolution_clock::now();
    auto recurDuration = duration_cast<microseconds>(stop - start);
    
    cout << "Iterative result: " << iterResult << endl;
    cout << "Iterative time: " << iterDuration.count() << " microseconds" << endl;
    cout << "Recursive result: " << recurResult << endl;
    cout << "Recursive time: " << recurDuration.count() << " microseconds" << endl;
    
    if (iterDuration < recurDuration) {
        cout << "Iterative was " << (recurDuration.count() - iterDuration.count())
             << " microseconds faster" << endl;
    } else {
        cout << "Recursive was " << (iterDuration.count() - recurDuration.count())
             << " microseconds faster" << endl;
    }
}
Fibonacci: Iteration vs Recursion with Multiple Approaches
#include <iostream>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;

// Fibonacci function prototypes
int fibonacciIterative(int n);
int fibonacciRecursive(int n);
int fibonacciMemoization(int n);
int fibonacciDP(int n);
void printFibonacciComparison(int terms);

int main() {
    cout << "=== FIBONACCI SEQUENCE: MULTIPLE APPROACHES ===" << endl << endl;
    
    // First 15 Fibonacci numbers
    cout << "First 15 Fibonacci numbers:" << endl;
    printFibonacciComparison(15);
    
    cout << endl << "==========================================" << endl << endl;
    
    // Performance comparison for larger n
    cout << "Performance Comparison (n=30):" << endl;
    cout << "Note: Naive recursion becomes extremely slow!" << endl << endl;
    
    int n = 30;
    
    // 1. Iterative (fastest)
    auto start = high_resolution_clock::now();
    int iterResult = fibonacciIterative(n);
    auto stop = high_resolution_clock::now();
    auto iterTime = duration_cast<microseconds>(stop - start);
    
    // 2. Memoization (optimized recursion)
    start = high_resolution_clock::now();
    int memoResult = fibonacciMemoization(n);
    stop = high_resolution_clock::now();
    auto memoTime = duration_cast<microseconds>(stop - start);
    
    // 3. Dynamic Programming
    start = high_resolution_clock::now();
    int dpResult = fibonacciDP(n);
    stop = high_resolution_clock::now();
    auto dpTime = duration_cast<microseconds>(stop - start);
    
    // 4. Naive Recursive (VERY SLOW for n=30)
    cout << "Warning: Naive recursive Fibonacci(30) takes significant time..." << endl;
    start = high_resolution_clock::now();
    int recurResult = fibonacciRecursive(n);
    stop = high_resolution_clock::now();
    auto recurTime = duration_cast<milliseconds>(stop - start);
    
    cout << endl << "Results for Fibonacci(" << n << "):" << endl;
    cout << "All methods should give: " << iterResult << endl << endl;
    
    cout << "Performance Summary:" << endl;
    cout << "1. Iterative:      " << iterTime.count() << " microseconds" << endl;
    cout << "2. Memoization:    " << memoTime.count() << " microseconds" << endl;
    cout << "3. Dynamic Prog:   " << dpTime.count() << " microseconds" << endl;
    cout << "4. Naive Recursion: " << recurTime.count() << " milliseconds" << endl;
    
    cout << endl << "==========================================" << endl << endl;
    
    // Demonstration of why naive recursion is slow
    cout << "Why Naive Recursion is Slow for Fibonacci:" << endl;
    cout << "fib(5) = fib(4) + fib(3)" << endl;
    cout << "fib(4) = fib(3) + fib(2)" << endl;
    cout << "fib(3) = fib(2) + fib(1)" << endl;
    cout << "Notice fib(3) is calculated twice!" << endl;
    cout << "For fib(30), same values are recalculated millions of times!" << endl;
    
    return 0;
}

// ITERATIVE FIBONACCI (Most efficient)
int fibonacciIterative(int n) {
    if (n < 0) return -1; // Error
    if (n <= 1) return n;
    
    int prev = 0, curr = 1;
    for (int i = 2; i <= n; i++) {
        int next = prev + curr;
        prev = curr;
        curr = next;
    }
    return curr;
}

// NAIVE RECURSIVE FIBONACCI (Inefficient - O(2^n))
int fibonacciRecursive(int n) {
    if (n < 0) return -1; // Error
    if (n <= 1) return n;
    
    return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}

// MEMOIZATION (Optimized recursion - O(n))
int fibonacciMemoHelper(int n, vector<int>& memo) {
    if (n <= 1) return n;
    
    // If already calculated, return cached value
    if (memo[n] != -1) {
        return memo[n];
    }
    
    // Calculate and store in memo
    memo[n] = fibonacciMemoHelper(n - 1, memo) + fibonacciMemoHelper(n - 2, memo);
    return memo[n];
}

int fibonacciMemoization(int n) {
    if (n < 0) return -1;
    
    vector<int> memo(n + 1, -1); // Initialize with -1 (not calculated)
    return fibonacciMemoHelper(n, memo);
}

// DYNAMIC PROGRAMMING (Bottom-up approach)
int fibonacciDP(int n) {
    if (n < 0) return -1;
    if (n <= 1) return n;
    
    vector<int> dp(n + 1);
    dp[0] = 0;
    dp[1] = 1;
    
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    
    return dp[n];
}

void printFibonacciComparison(int terms) {
    cout << "n\tIterative\tRecursive\tMemoization\tDP" << endl;
    cout << "-\t---------\t---------\t-----------\t--" << endl;
    
    for (int i = 0; i < terms; i++) {
        cout << i << "\t" 
             << fibonacciIterative(i) << "\t\t"
             << fibonacciRecursive(i) << "\t\t"
             << fibonacciMemoization(i) << "\t\t"
             << fibonacciDP(i) << endl;
    }
}
Iterative Factorial
  • Time: O(n)
  • Space: O(1)
  • Uses simple loop
  • No stack overhead
  • Best for performance
Recursive Factorial
  • Time: O(n)
  • Space: O(n) for stack
  • Elegant mathematical definition
  • Risk of stack overflow for large n
  • Slower due to function calls
Fibonacci Insights
  • Naive recursion: O(2ⁿ) - exponential!
  • Iterative/DP: O(n) - linear
  • Memoization: O(n) with recursion
  • Demonstrates importance of optimization
  • Classic example of overlapping subproblems

2. Recursion Stack Visualization

Understanding the call stack is crucial for debugging recursive functions. Each recursive call creates a new stack frame with its own parameters and local variables.

Stack Frame Anatomy for factorialRecursive(5)
Stack Frame #6: factorialRecursive(1)
n = 1
Base case reached!
Returns: 1
Frame destroyed after return
Stack Frame #5: factorialRecursive(2)
n = 2
Waiting for: factorialRecursive(1)
Will calculate: 2 * [result from frame #6]
Stack Frame #4: factorialRecursive(3)
n = 3
Waiting for: factorialRecursive(2)
Will calculate: 3 * [result from frame #5]
Stack Frame #3: factorialRecursive(4)
n = 4
Waiting for: factorialRecursive(3)
Will calculate: 4 * [result from frame #4]
Stack Frame #2: factorialRecursive(5) ← CURRENTLY EXECUTING
n = 5
Waiting for: factorialRecursive(4)
Will calculate: 5 * [result from frame #3]
Stack Frame #1: main()
Called: factorialRecursive(5)
Waiting for final result
Will print: 120
↑ Stack grows downward ↑ | Current execution marked
Visualizing Recursion Stack with Debug Output
#include <iostream>
#include <string>
using namespace std;

// Function to visualize recursion depth
void printIndent(int depth) {
    for (int i = 0; i < depth; i++) {
        cout << "|   ";
    }
}

// Factorial with stack visualization
long long factorialVisual(int n, int depth = 0) {
    printIndent(depth);
    cout << "→ factorial(" << n << ") called" << endl;
    
    if (n < 0) {
        printIndent(depth);
        cout << "← ERROR: Negative number!" << endl;
        throw invalid_argument("Negative number");
    }
    
    if (n <= 1) {
        printIndent(depth);
        cout << "← BASE CASE: returning 1" << endl;
        return 1;
    }
    
    printIndent(depth);
    cout << "   Need to calculate " << n << " * factorial(" << n-1 << ")" << endl;
    
    // Recursive call
    long long smallerFact = factorialVisual(n - 1, depth + 1);
    
    long long result = n * smallerFact;
    
    printIndent(depth);
    cout << "← factorial(" << n << ") = " << n << " * " << smallerFact 
         << " = " << result << endl;
    
    return result;
}

// Fibonacci with stack visualization (naive version)
int fibonacciVisual(int n, int depth = 0) {
    printIndent(depth);
    cout << "→ fibonacci(" << n << ") called" << endl;
    
    if (n < 0) {
        printIndent(depth);
        cout << "← ERROR: Negative number!" << endl;
        return -1;
    }
    
    if (n <= 1) {
        printIndent(depth);
        cout << "← BASE CASE: returning " << n << endl;
        return n;
    }
    
    printIndent(depth);
    cout << "   Need fibonacci(" << n-1 << ") + fibonacci(" << n-2 << ")" << endl;
    
    // Two recursive calls - demonstrates exponential growth
    int fib1 = fibonacciVisual(n - 1, depth + 1);
    
    printIndent(depth);
    cout << "   Got fibonacci(" << n-1 << ") = " << fib1 
         << ", now need fibonacci(" << n-2 << ")" << endl;
    
    int fib2 = fibonacciVisual(n - 2, depth + 1);
    
    int result = fib1 + fib2;
    
    printIndent(depth);
    cout << "← fibonacci(" << n << ") = " << fib1 << " + " << fib2 
         << " = " << result << endl;
    
    return result;
}

int main() {
    cout << "=== RECURSION STACK VISUALIZATION ===" << endl << endl;
    
    // Example 1: Factorial visualization
    cout << "Example 1: factorialVisual(5)" << endl;
    cout << "Call stack visualization:" << endl;
    cout << "==========================" << endl;
    long long fact5 = factorialVisual(5);
    cout << "==========================" << endl;
    cout << "Final result: " << fact5 << endl;
    
    cout << endl << "=====================================" << endl << endl;
    
    // Example 2: Fibonacci visualization (small n)
    cout << "Example 2: fibonacciVisual(4)" << endl;
    cout << "Call stack visualization:" << endl;
    cout << "==========================" << endl;
    cout << "Note: Same values are recalculated!" << endl;
    int fib4 = fibonacciVisual(4);
    cout << "==========================" << endl;
    cout << "Final result: " << fib4 << endl;
    
    cout << endl << "=====================================" << endl << endl;
    
    // Example 3: Stack overflow demonstration
    cout << "Example 3: Stack Overflow Risk" << endl;
    cout << "Trying factorialVisual(10000)..." << endl;
    
    try {
        // This will likely cause stack overflow
        // long long hugeFact = factorialVisual(10000);
        // cout << "Result: " << hugeFact << endl;
        cout << "(Commented out to prevent crash)" << endl;
    } catch (const exception& e) {
        cout << "Error: " << e.what() << endl;
    }
    
    cout << endl << "Recursive calls create stack frames." << endl;
    cout << "Each frame contains:" << endl;
    cout << "1. Parameters" << endl;
    cout << "2. Local variables" << endl;
    cout << "3. Return address" << endl;
    cout << "4. Previous frame pointer" << endl;
    cout << endl << "Stack size is limited (typically 1-8 MB)" << endl;
    
    return 0;
}
Stack Overflow Dangers:
  • Each recursive call uses stack memory (typically 1-8 MB total)
  • Deep recursion can exhaust stack space
  • Common with: No base case, incorrect base case, too deep recursion
  • Symptoms: Program crashes with segmentation fault
  • Solutions: Use iteration, increase stack size, optimize recursion

3. When to Use Recursion vs Iteration: Decision Guide

Choosing between recursion and iteration depends on problem characteristics, performance requirements, and code clarity.

Problem Type Use Iteration When... Use Recursion When...
Tree/Graph Traversal Not ideal
Requires explicit stack/queue
More complex implementation
Excellent fit
DFS naturally recursive
Clean, readable code
Examples: Binary tree traversal
Mathematical Calculations Usually better
Factorial, Fibonacci (iterative)
Better performance
No stack overflow risk
Sometimes useful
Matches mathematical definition
Readable for simple cases
Risk for large inputs
Divide and Conquer Challenging
Merge sort (iterative is complex)
Quick sort (needs explicit stack)
Natural fit
Merge sort (clean recursive)
Quick sort (elegant recursive)
Binary search (recursive)
Backtracking Problems Very complex
N-Queens (hard with iteration)
Sudoku solver (messy iterative)
Perfect match
N-Queens (clean recursive)
Sudoku solver (elegant)
Maze solving (natural)
Dynamic Programming Usually preferred
Bottom-up DP (iterative)
Better space optimization
No recursion overhead
Top-down approach
Memoization (recursive + cache)
Easier to implement initially
Can be optimized later
File System Navigation Complex
Need explicit stack
Harder to read/maintain
Ideal solution
Directory traversal
Clean, readable code
Matches hierarchical structure
Performance Critical Always better
No function call overhead
Better cache performance
Predictable performance
Avoid if possible
Function call overhead
Stack memory usage
Possible cache misses
Code Readability Simpler for linear problems
Easier for beginners
Direct control flow
Elegant for certain problems
Expresses problem nature
Can be more declarative
Real-World Examples: Choosing the Right Approach
#include <iostream>
#include <vector>
#include <stack>
#include <queue>
using namespace std;

// Tree node structure
struct TreeNode {
    int value;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int val) : value(val), left(nullptr), right(nullptr) {}
};

// Example 1: Binary Tree Traversal
TreeNode* createSampleTree() {
    // Create tree:       1
    //                  /   \
    //                 2     3
    //                / \   /
    //               4   5 6
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);
    root->right->left = new TreeNode(6);
    return root;
}

// Recursive In-order Traversal (Natural fit)
void inorderRecursive(TreeNode* node) {
    if (node == nullptr) return;
    
    inorderRecursive(node->left);
    cout << node->value << " ";
    inorderRecursive(node->right);
}

// Iterative In-order Traversal (More complex)
void inorderIterative(TreeNode* root) {
    stack<TreeNode*> stk;
    TreeNode* current = root;
    
    while (current != nullptr || !stk.empty()) {
        // Reach leftmost node
        while (current != nullptr) {
            stk.push(current);
            current = current->left;
        }
        
        current = stk.top();
        stk.pop();
        
        cout << current->value << " ";
        
        current = current->right;
    }
}

// Example 2: Directory-like structure
struct Directory {
    string name;
    vector<Directory*> subdirectories;
    vector<string> files;
    
    Directory(string n) : name(n) {}
};

// Recursive directory traversal (Natural)
void listDirectoryRecursive(Directory* dir, int depth = 0) {
    string indent(depth * 2, ' ');
    
    cout << indent << dir->name << "/" << endl;
    
    // List files
    for (const string& file : dir->files) {
        cout << indent << "  " << file << endl;
    }
    
    // Recursively list subdirectories
    for (Directory* subdir : dir->subdirectories) {
        listDirectoryRecursive(subdir, depth + 1);
    }
}

// Iterative directory traversal (Complex)
void listDirectoryIterative(Directory* root) {
    stack<pair<Directory*, int>> stk;
    stk.push({root, 0});
    
    while (!stk.empty()) {
        auto [current, depth] = stk.top();
        stk.pop();
        
        string indent(depth * 2, ' ');
        cout << indent << current->name << "/" << endl;
        
        // List files
        for (const string& file : current->files) {
            cout << indent << "  " << file << endl;
        }
        
        // Push subdirectories in reverse order for correct traversal
        for (auto it = current->subdirectories.rbegin(); 
             it != current->subdirectories.rend(); ++it) {
            stk.push({*it, depth + 1});
        }
    }
}

// Example 3: Simple array processing (Better with iteration)
int sumArrayIterative(const vector<int>& arr) {
    int sum = 0;
    for (int num : arr) {
        sum += num;
    }
    return sum;
}

int sumArrayRecursive(const vector<int>& arr, int index = 0) {
    if (index >= arr.size()) return 0;
    return arr[index] + sumArrayRecursive(arr, index + 1);
}

int main() {
    cout << "=== REAL-WORLD RECURSION vs ITERATION EXAMPLES ===" << endl << endl;
    
    // Example 1: Tree Traversal
    cout << "Example 1: Binary Tree In-order Traversal" << endl;
    TreeNode* tree = createSampleTree();
    
    cout << "Recursive: ";
    inorderRecursive(tree);
    cout << endl;
    
    cout << "Iterative: ";
    inorderIterative(tree);
    cout << endl;
    
    cout << "Verdict: Recursion is cleaner for tree traversal" << endl;
    
    cout << endl << "===========================================" << endl << endl;
    
    // Example 2: Directory Structure
    cout << "Example 2: Directory Structure Traversal" << endl;
    
    Directory* root = new Directory("root");
    root->files = {"readme.txt", "config.ini"};
    
    Directory* docs = new Directory("documents");
    docs->files = {"report.pdf", "notes.txt"};
    root->subdirectories.push_back(docs);
    
    Directory* pics = new Directory("pictures");
    pics->files = {"photo1.jpg", "photo2.jpg"};
    root->subdirectories.push_back(pics);
    
    Directory* vacation = new Directory("vacation");
    vacation->files = {"beach.jpg", "mountain.png"};
    pics->subdirectories.push_back(vacation);
    
    cout << "Recursive traversal:" << endl;
    listDirectoryRecursive(root);
    
    cout << endl << "Iterative traversal:" << endl;
    listDirectoryIterative(root);
    
    cout << "Verdict: Recursion is more natural for hierarchical structures" << endl;
    
    cout << endl << "===========================================" << endl << endl;
    
    // Example 3: Array Processing
    cout << "Example 3: Array Summation" << endl;
    vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    cout << "Iterative sum: " << sumArrayIterative(numbers) << endl;
    cout << "Recursive sum: " << sumArrayRecursive(numbers) << endl;
    
    cout << "Verdict: Iteration is simpler and more efficient for linear arrays" << endl;
    
    cout << endl << "===========================================" << endl << endl;
    
    // Cleanup
    // Note: In real code, properly delete allocated memory
    cout << "Decision Guidelines:" << endl;
    cout << "1. Use RECURSION for:" << endl;
    cout << "   • Tree/graph traversal" << endl;
    cout << "   • Divide and conquer algorithms" << endl;
    cout << "   • Backtracking problems" << endl;
    cout << "   • Hierarchical data structures" << endl;
    cout << endl;
    cout << "2. Use ITERATION for:" << endl;
    cout << "   • Simple loops/linear processing" << endl;
    cout << "   • Performance-critical code" << endl;
    cout << "   • When stack depth could be large" << endl;
    cout << "   • When recursion doesn't simplify code" << endl;
    
    return 0;
}

4. Tail Recursion Optimization

Tail recursion occurs when the recursive call is the last operation in the function. Compilers can optimize tail recursion to avoid stack growth, making it as efficient as iteration.

Tail Recursion Pattern
// REGULAR RECURSION (not tail)
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1); // Multiplication AFTER call
}

// TAIL RECURSION (can be optimized)
int factorialTail(int n, int accumulator = 1) {
    if (n <= 1) return accumulator;
    return factorialTail(n - 1, n * accumulator); // Call is LAST operation
}
Tail Recursion Examples and Optimization
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;

// Regular recursion (NOT tail recursive)
int sumRegular(int n) {
    if (n <= 0) return 0;
    return n + sumRegular(n - 1); // Addition happens AFTER return
}

// Tail recursive version
int sumTail(int n, int accumulator = 0) {
    if (n <= 0) return accumulator;
    return sumTail(n - 1, accumulator + n); // Recursive call is LAST operation
}

// Regular factorial (NOT tail recursive)
long long factorialRegular(int n) {
    if (n <= 1) return 1;
    return n * factorialRegular(n - 1); // Multiplication AFTER call
}

// Tail recursive factorial
long long factorialTail(int n, long long accumulator = 1) {
    if (n <= 1) return accumulator;
    return factorialTail(n - 1, n * accumulator); // Call is LAST operation
}

// GCD using Euclid's algorithm (naturally tail recursive)
int gcdRegular(int a, int b) {
    if (b == 0) return a;
    return gcdRegular(b, a % b); // Naturally tail recursive!
}

// Fibonacci tail recursive (accumulator pattern)
int fibonacciTail(int n, int a = 0, int b = 1) {
    if (n == 0) return a;
    if (n == 1) return b;
    return fibonacciTail(n - 1, b, a + b);
}

// Helper function to test stack usage
void testDeepRecursion(int n, int& maxDepth, int currentDepth = 1) {
    maxDepth = max(maxDepth, currentDepth);
    
    if (n <= 0) return;
    
    // Force deep recursion to test stack limits
    testDeepRecursion(n - 1, maxDepth, currentDepth + 1);
}

int main() {
    cout << "=== TAIL RECURSION OPTIMIZATION ===" << endl << endl;
    
    // Example 1: Sum of first n numbers
    cout << "Example 1: Sum of first 100 numbers" << endl;
    cout << "Regular recursion: " << sumRegular(100) << endl;
    cout << "Tail recursion:    " << sumTail(100) << endl;
    cout << "Mathematical:      " << (100 * 101) / 2 << endl;
    
    cout << endl << "=================================" << endl << endl;
    
    // Example 2: Factorial comparison
    cout << "Example 2: Factorial of 10" << endl;
    cout << "Regular: " << factorialRegular(10) << endl;
    cout << "Tail:    " << factorialTail(10) << endl;
    
    cout << endl << "=================================" << endl << endl;
    
    // Example 3: GCD (naturally tail recursive)
    cout << "Example 3: GCD of 48 and 18" << endl;
    cout << "GCD(48, 18) = " << gcdRegular(48, 18) << endl;
    
    cout << endl << "=================================" << endl << endl;
    
    // Example 4: Fibonacci with tail recursion
    cout << "Example 4: Fibonacci(10) with different approaches" << endl;
    cout << "Naive recursive: Too slow for demonstration" << endl;
    cout << "Tail recursive:  " << fibonacciTail(10) << endl;
    
    cout << endl << "=================================" << endl << endl;
    
    // Performance comparison
    cout << "Performance Comparison (n=10000 for sum):" << endl;
    
    // WARNING: Regular recursion may cause stack overflow!
    cout << "Note: Regular recursion with n=10000 may crash!" << endl;
    
    // Test with smaller number to avoid crash
    int testN = 1000;
    
    auto start = high_resolution_clock::now();
    int tailResult = sumTail(testN);
    auto stop = high_resolution_clock::now();
    auto tailTime = duration_cast<microseconds>(stop - start);
    
    cout << "Tail recursion (n=" << testN << "): " << tailTime.count() 
         << " microseconds" << endl;
    
    // Regular recursion with smaller number to avoid stack overflow
    testN = 500; // Smaller to prevent crash
    start = high_resolution_clock::now();
    int regularResult = sumRegular(testN);
    stop = high_resolution_clock::now();
    auto regularTime = duration_cast<microseconds>(stop - start);
    
    cout << "Regular recursion (n=" << testN << "): " << regularTime.count() 
         << " microseconds" << endl;
    
    cout << endl << "=================================" << endl << endl;
    
    // How tail recursion optimization works
    cout << "How Tail Recursion Optimization Works:" << endl;
    cout << "1. Regular recursion:" << endl;
    cout << "   sum(5) = 5 + sum(4)" << endl;
    cout << "   sum(4) = 4 + sum(3)" << endl;
    cout << "   ... Each call waits for result" << endl;
    cout << endl;
    cout << "2. Tail recursion (after optimization):" << endl;
    cout << "   sumTail(5, 0)" << endl;
    cout << "   sumTail(4, 5)" << endl;
    cout << "   sumTail(3, 9)" << endl;
    cout << "   ... Current stack frame REUSED!" << endl;
    cout << endl;
    cout << "Compiler optimization (tail call elimination):" << endl;
    cout << "• Instead of new stack frame, reuse current" << endl;
    cout << "• Update parameters and jump to start" << endl;
    cout << "• Essentially becomes a loop!" << endl;
    
    cout << endl << "=================================" << endl << endl;
    
    // Testing stack depth
    cout << "Testing Maximum Recursion Depth:" << endl;
    int maxDepth = 0;
    
    // Test with safe value
    testDeepRecursion(100, maxDepth);
    cout << "Maximum recursion depth reached: " << maxDepth << endl;
    cout << "Typical stack limits: 1MB to 8MB" << endl;
    cout << "Each call uses ~100-500 bytes" << endl;
    cout << "Max safe depth: ~1000-8000 calls" << endl;
    
    return 0;
}
Tail Recursion Rules:
  • Recursive call must be the LAST operation in the function
  • No operations should happen after the recursive call
  • Use accumulator parameters to carry results forward
  • Modern C++ compilers (GCC, Clang) optimize tail recursion with -O2 or -O3
  • MSVC may not optimize all tail recursion cases
  • Always check if your compiler optimizes tail recursion

5. Best Practices and Common Mistakes

Recursion Best Practices
  • Always define a base case first
  • Ensure progress toward base case
  • Use tail recursion when possible
  • Consider memoization for overlapping subproblems
  • Test with edge cases (0, 1, negative, large values)
  • Document recursion depth expectations
  • Consider iterative alternatives for performance
Common Recursion Mistakes
  • Missing base case (infinite recursion)
  • Base case never reached (incorrect progress)
  • Forgetting return statement in recursive case
  • Stack overflow with deep recursion
  • Exponential time complexity (Fibonacci naive)
  • Using recursion where iteration is better
  • Not considering memoization for repeated calculations
Recursion: Good vs Bad Practices
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

// ===== BAD PRACTICES =====

// BAD: Missing base case (will crash)
int badFactorial(int n) {
    // Missing: if (n <= 1) return 1;
    return n * badFactorial(n - 1); // Infinite recursion!
}

// BAD: Incorrect base case (may never reach)
int badSum(int n) {
    if (n == 1) return 1; // What about n=0? n=negative?
    return n + badSum(n - 1);
}

// BAD: No progress toward base case
int infiniteRecursion(int n) {
    if (n == 0) return 0;
    return infiniteRecursion(n); // Same n, never decreases!
}

// BAD: Using recursion for simple iteration
void printNumbersBad(int n) {
    if (n <= 0) return;
    printNumbersBad(n - 1);
    cout << n << " ";
    // Simple loop would be better: for(int i=1; i<=n; i++) cout< 0) {
        sum += n % 10;
        n /= 10;
    }
    return sum;
}

// GOOD: Using recursion for natural problems
struct FileNode {
    string name;
    bool isDirectory;
    vector<FileNode*> children;
    
    FileNode(string n, bool dir) : name(n), isDirectory(dir) {}
};

// Natural use of recursion: File system traversal
void listFilesRecursive(FileNode* node, int depth = 0) {
    string indent(depth * 2, ' ');
    cout << indent << node->name << (node->isDirectory ? "/" : "") << endl;
    
    if (node->isDirectory) {
        for (FileNode* child : node->children) {
            listFilesRecursive(child, depth + 1);
        }
    }
}

int main() {
    cout << "=== RECURSION: GOOD vs BAD PRACTICES ===" << endl << endl;
    
    // Good practices demonstration
    cout << "Good Practices Examples:" << endl;
    cout << "1. Proper factorial(5): " << goodFactorial(5) << endl;
    cout << "2. Tail recursive factorial(5): " << goodFactorialTail(5) << endl;
    cout << "3. Sum of digits(12345): " << sumDigits(12345) << endl;
    cout << "4. Iterative sum of digits(12345): " << sumDigitsIterative(12345) << endl;
    
    cout << endl << "Fibonacci with Memoization (n=40):" << endl;
    cout << "This would be impossible with naive recursion!" << endl;
    cout << "fibonacciMemoized(40) = " << fibonacciMemoized(40) << endl;
    
    cout << endl << "=====================================" << endl << endl;
    
    // Creating a sample file structure
    cout << "Natural Recursion Example: File System" << endl;
    
    FileNode* root = new FileNode("root", true);
    FileNode* docs = new FileNode("documents", true);
    FileNode* pics = new FileNode("pictures", true);
    
    root->children.push_back(docs);
    root->children.push_back(pics);
    root->children.push_back(new FileNode("readme.txt", false));
    
    docs->children.push_back(new FileNode("report.pdf", false));
    docs->children.push_back(new FileNode("notes.txt", false));
    
    pics->children.push_back(new FileNode("photo1.jpg", false));
    pics->children.push_back(new FileNode("photo2.jpg", false));
    
    FileNode* vacation = new FileNode("vacation", true);
    vacation->children.push_back(new FileNode("beach.jpg", false));
    pics->children.push_back(vacation);
    
    cout << "File structure:" << endl;
    listFilesRecursive(root);
    
    cout << endl << "=====================================" << endl << endl;
    
    cout << "Recursion Guidelines Summary:" << endl;
    cout << "✓ DO use recursion for:" << endl;
    cout << "   • Tree/graph traversal" << endl;
    cout << "   • Divide and conquer" << endl;
    cout << "   • Backtracking problems" << endl;
    cout << "   • When it simplifies code significantly" << endl;
    cout << endl;
    cout << "✓ DO NOT use recursion for:" << endl;
    cout << "   • Simple linear iteration" << endl;
    cout << "   • Performance-critical code" << endl;
    cout << "   • When recursion depth could be large" << endl;
    cout << "   • When iterative solution is equally clear" << endl;
    cout << endl;
    cout << "✓ ALWAYS:" << endl;
    cout << "   • Define a base case" << endl;
    cout << "   • Ensure progress toward base case" << endl;
    cout << "   • Consider stack limitations" << endl;
    cout << "   • Test with edge cases" << endl;
    cout << "   • Consider memoization for repeated calculations" << endl;
    
    // Cleanup (in real code, use smart pointers)
    delete root;
    delete docs;
    delete pics;
    
    return 0;
}