Recursion

Recursion is a powerful programming technique where a function calls itself to solve smaller instances of the same problem. It’s fundamental to many algorithmic patterns and essential for problems involving trees, graphs, backtracking, and divide-and-conquer strategies.

Real-World Analogy

Think of recursion like organizing your photo album.

Without Recursion: You try to organize all photos at once. Every time you pick up a photo, you have to think about every other photo to decide where it goes. This quickly becomes overwhelming.

With Recursion: You use a simple strategy:

  1. Base Case: If you have only 1 or 2 photos left, just organize them directly.
  2. Recursive Case: If you have many photos, split them into two piles, organize each pile separately (by calling the same organizing process), then combine the organized piles.

This way, you never have to think about more than a few photos at a time. The same organizing logic applies at every scale, whether you’re organizing 5 photos or 5000.

Visual Explanation

Let’s visualize how recursion works using a factorial calculation example. Factorial of 4 (4!) means 4 x 3 x 2 x 1.

  graph TD
    subgraph RecursiveCalls ["Recursive Call Stack"]
        direction TB
        FC4["factorial(4)"]
        FC4 -->|calls| FC3["factorial(3)"]
        FC3 -->|calls| FC2["factorial(2)"]
        FC2 -->|calls| FC1["factorial(1)"]

        style FC4 fill:#f9d,stroke:#333
        style FC1 fill:#aaf,stroke:#333
    end

    subgraph ReturnValues ["Return Values (Unwinding)"]
        direction TB
        RV1["return 1"]
        RV1 -->|4 * 1 = 4| RV2["return 2"]
        RV2 -->|3 * 2 = 6| RV3["return 6"]
        RV3 -->|4 * 6 = 24| RV4["return 24"]

        style RV1 fill:#aaf,stroke:#333
        style RV4 fill:#f9d,stroke:#333
    end

    FC1 -.->|Base Case| RV1
    RV1 -.->|Unwind| FC2
    RV2 -.->|Unwind| FC3
    RV3 -.->|Unwind| FC4

Explanation:

  • Going Down: Each call pauses and makes another call, creating a stack of pending operations.
  • Base Case: factorial(1) returns directly (no more recursion needed).
  • Coming Back Up: Each paused call receives the result, does its multiplication, and returns to its caller.

When to Use Recursion

Use this pattern when you see these characteristics:

  • Problem can be broken down into smaller instances of the same problem
  • You’re working with tree or graph data structures
  • You need to explore all possibilities (combinations, permutations, subsets)
  • Problem has optimal substructure (DP with memoization)
  • You need divide-and-conquer approach (merge sort, quick sort, binary search)
  • The problem naturally lends itself to a “solve subproblems, combine results” approach

Complexity Analysis

Time Complexity

TypeComplexityWhen It Occurs
Linear Recursion
O(N)
Single recursive call each step (factorial, linear search)
Tree Recursion
O(2^N)
Multiple recursive calls (naive Fibonacci)
With Memoization
O(N)
Caching results eliminates redundant work
Divide & Conquer
O(N log N)
Splitting into halves recursively (merge sort)

Derivation:

  • Linear: Each problem reduces size by 1, so N calls total.
  • Tree (Fibonacci): Each call spawns 2 more, creating a binary tree of 2^N nodes.
  • Memoization: Each subproblem computed once, stored, and reused.

Space Complexity

TypeComplexityExplanation
Call Stack
O(H)
H = recursion depth (worst case O(N))
Memoization Cache
O(N)
Storing computed subproblems
Total (with memo)
O(N)
Stack + cache combined

Derivation:

  • Call Stack: Each recursive call uses stack space proportional to depth.
  • Cache: Storing N distinct subproblems requires O(N) space.

Common Mistakes

  1. Missing Base Case: Never returns, causing infinite recursion and stack overflow.
  2. Incorrect Base Case: Returns too early, missing valid solutions.
  3. Shared State Issues: Modifying arrays/objects when backtracking without proper cleanup.
  4. Duplicate Computations: Not using memoization for overlapping subproblems.
  5. Wrong Recursion Parameters: Passing incorrect values to recursive calls, breaking subproblem logic.
Pro Tip: Recursion is elegant for tree and graph problems, but iteration is often more space-efficient. Always consider both approaches and choose based on constraints.

Next Steps

Ready to implement recursive solutions? Check out our Code Templates for reusable patterns in multiple languages, or dive into Practice Problems to apply what you’ve learned.