ENGIMY.IO - CHEATSHEET
RECURSION vs ITERATION × QUICK REFERENCE
REFERENCE v1.0

Recursion vs Iteration

Understanding the trade‑offs, patterns, and conversions.

What is Recursion?

A function that calls itself to solve a smaller subproblem.

Components of a Recursive Function

  • Base Case – stops recursion, provides answer for smallest input
  • Recursive Case – reduces problem size, calls itself
  • Return – combines results from recursive calls
int factorial(int n) {
    // Base case
    if (n <= 1) return 1;
    // Recursive case
    return n * factorial(n - 1);
}

What is Iteration?

A loop that repeats a block of code until a condition is met.

Common Loop Types

  • for – known number of iterations
  • while – condition‑based, unknown count
  • do‑while – executes at least once
  • for‑each – iterates over collection
int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

Comparison Table

Aspect Recursion Iteration
Code clarity Often cleaner (especially for trees/graphs) Can be verbose
Space complexity O(n) call stack O(1) (usually)
Time complexity Same as iteration (except overhead) Same as recursion (faster due to no overhead)
Stack overflow risk Yes – for deep recursion No
Debugging Harder (multiple stack frames) Easier (single frame)
Use cases Trees, graphs, backtracking Simple loops, arrays

When to Use Recursion

Good for Recursion
  • Tree traversal (DFS, BFS)
  • Graph traversal
  • Divide and conquer
  • Backtracking (N‑Queens, Sudoku)
  • Dynamic Programming (memoization)
  • Mathematical problems (Fibonacci, factorial)
  • Problems with recursive structure
Not Ideal for Recursion
  • Simple loops (1..n)
  • Large input sizes (stack overflow)
  • Performance‑critical code
  • Embedded systems (limited stack)
  • When iteration is simpler and clearer

When to Use Iteration

Good for Iteration
  • Traversing arrays/lists
  • Simple counting
  • Performance‑critical code
  • Large input sizes
  • Memory‑constrained environments
  • When clarity is not compromised
Not Ideal for Iteration
  • Tree/Graph traversal (can be complex)
  • Backtracking
  • Problems with recursive structure
  • When recursion reduces code complexity

Recursion Patterns

1. Direct Recursion

int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

2. Tail Recursion (Optimized)

int factorialTail(int n, int acc) {
    if (n <= 1) return acc;
    return factorialTail(n - 1, n * acc);
}
// Call: factorialTail(5, 1) → 120

3. Mutual Recursion

boolean isEven(int n) {
    if (n == 0) return true;
    return isOdd(n - 1);
}
boolean isOdd(int n) {
    if (n == 0) return false;
    return isEven(n - 1);
}

4. Multiple Recursion (Branching)

int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

5. Nested Recursion

int ackermann(int m, int n) {
    if (m == 0) return n + 1;
    if (n == 0) return ackermann(m - 1, 1);
    return ackermann(m - 1, ackermann(m, n - 1));
}

Conversion: Recursion → Iteration

Method 1: Use a Stack (Manual Call Stack)

// Recursive DFS
void dfs(TreeNode root) {
    if (root == null) return;
    System.out.print(root.val);
    dfs(root.left);
    dfs(root.right);
}

// Iterative DFS (using Stack)
void dfsIterative(TreeNode root) {
    if (root == null) return;
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode curr = stack.pop();
        System.out.print(curr.val);
        if (curr.right != null) stack.push(curr.right);
        if (curr.left != null) stack.push(curr.left);
    }
}

Method 2: Use a Queue (for BFS)

// Recursive BFS (usually done iteratively)
// Iterative BFS (Queue)
void bfs(TreeNode root) {
    if (root == null) return;
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);
    while (!q.isEmpty()) {
        TreeNode curr = q.poll();
        System.out.print(curr.val);
        if (curr.left != null) q.offer(curr.left);
        if (curr.right != null) q.offer(curr.right);
    }
}

Method 3: Tail Recursion Elimination

// Tail recursive
int sumTail(int n, int acc) {
    if (n == 0) return acc;
    return sumTail(n - 1, acc + n);
}

// Iterative (no stack)
int sumIterative(int n) {
    int acc = 0;
    while (n > 0) {
        acc += n;
        n--;
    }
    return acc;
}

Common Problems – Recursive vs Iterative

Factorial

Recursive
int fact(int n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}
Iterative
int fact(int n) {
    int r = 1;
    for (int i = 2; i <= n; i++) r *= i;
    return r;
}

Fibonacci

Recursive (exponential)
int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}
Iterative (linear)
int fib(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
        int c = a + b;
        a = b;
        b = c;
    }
    return b;
}

Reverse Linked List

Recursive
ListNode reverse(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode newHead = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
}
Iterative
ListNode reverse(ListNode head) {
    ListNode prev = null, curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

Binary Tree Traversal (In‑order)

Recursive
void inorder(TreeNode root) {
    if (root == null) return;
    inorder(root.left);
    System.out.print(root.val);
    inorder(root.right);
}
Iterative (Stack)
void inorder(TreeNode root) {
    Stack<TreeNode> st = new Stack<>();
    TreeNode curr = root;
    while (curr != null || !st.isEmpty()) {
        while (curr != null) {
            st.push(curr);
            curr = curr.left;
        }
        curr = st.pop();
        System.out.print(curr.val);
        curr = curr.right;
    }
}

Tail Recursion Optimization

  • Tail recursion – recursive call is the last operation
  • Compiler optimises – eliminates call stack (TCO)
  • Supported in: Scala, Kotlin, Clojure, Erlang, Haskell, and some C/C++ compilers
  • Not supported in: Java, Python (by default), JavaScript (limited)

Tail Recursive Example

// Non‑tail recursive (multiply after recursion)
int fact1(int n) {
    if (n <= 1) return 1;
    return n * fact1(n - 1);  // not tail – multiplication after
}

// Tail recursive (no operation after recursion)
int fact2(int n, int acc) {
    if (n <= 1) return acc;
    return fact2(n - 1, n * acc);  // tail – last operation is call
}

Recursion Pitfalls

  • Stack Overflow – deep recursion exceeds call stack limit
  • Exponential Time – naive recursion (e.g., Fibonacci) recomputes subproblems
  • Infinite Recursion – missing or incorrect base case
  • Debugging Difficult – multiple nested calls make tracing hard
  • Memory Overhead – each call uses stack space

Iteration Pitfalls

  • Infinite Loops – incorrect loop condition
  • Off‑by‑One Errors – index boundaries
  • Complex State – need to manage multiple variables
  • Less Readable – for deeply recursive problems

Choosing Between Recursion and Iteration

Factor Choose Recursion Choose Iteration
Problem structure Tree, graph, backtracking Linear, sequential
Code clarity More natural for recursive problems More natural for simple loops
Performance Slightly slower (call overhead) Faster (no call overhead)
Memory O(n) stack O(1) extra (usually)
Input size Small to medium Any (no stack overflow)
Tail call optimisation If supported, O(1) stack Not needed

Common Recursive Problem Categories

Tree Problems
  • Traversals (pre/in/post/level)
  • Height/Depth
  • Diameter
  • LCA
  • Balanced check
  • Sum of paths
Graph Problems
  • DFS (connected components)
  • Topological sort
  • Cycle detection
  • Bipartite check
  • Articulation points
  • MST (Prim's)
Backtracking
  • N‑Queens
  • Sudoku Solver
  • Permutations
  • Subsets
  • Combinations
  • Word Search
Divide & Conquer
  • Merge Sort
  • Quick Sort
  • Binary Search
  • Maximum Subarray
  • Closest Pair
  • Strassen's Matrix

Memoization (Recursive + Caching)

int fibMemo(int n, int[] memo) {
    if (n <= 1) return n;
    if (memo[n] != 0) return memo[n];
    memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
    return memo[n];
}

// Time: O(n) – Space: O(n) (similar to iterative DP)
// Best of both worlds – recursive clarity + iterative performance

Summary

  • Use recursion – for tree/graph traversal, backtracking, divide and conquer
  • Use iteration – for simple loops, performance‑critical code, large inputs
  • Consider memoization – to optimise recursive problems with overlapping subproblems
  • Consider tail recursion – if supported, gives O(1) stack
  • Convert when needed – use stack/queue to convert recursion to iteration
📌 Quick Reference
Recursion: function calls itself – base + recursive case
Iteration: loops – for, while, do‑while
Tail recursion: last operation is recursive call – optimizable
Memoization: recursion + caching – O(n) time instead of exponential
Stack overflow: risk with deep recursion – use iteration or tail recursion
← Back to All Cheatsheets