Mastering Recursion: 10 Powerful Patterns Every Developer Should Know

Recursion isn’t just some abstract idea β€” it’s like a toolbox filled with patterns. A lot of the problems that seem daunting in recursion can actually be solved once you identify the right pattern to use.

In this post, I’m going to walk you through 10 distinct recursion techniques β€” each one featuring:

  • When to apply it

  • A handy trick to remember

  • The structure of the base case

  • A code template to guide you

  • The best LeetCode problems to practice with

1. Pick / Not Pick Pattern

  • When to Use: Choose or skip an element (e.g., sum, selection)

  • Trick: pick = nums[i] + f(i+2), notPick = f(i+1)

  • Base Case: Index out of bounds

javaCopyEditint solve(int[] nums, int i) {
  if (i >= nums.length) return 0;
  int pick = nums[i] + solve(nums, i + 2);
  int notPick = solve(nums, i + 1);
  return Math.max(pick, notPick);
}

LeetCode Practice:


2. Count All Ways (Total Paths)

  • When to Use: Count total valid paths/ways

  • Trick: return left + right

  • Base Case: Return 1 if path is valid, 0 if invalid

javaCopyEditint countPaths(int i, int j) {
  if (outOfBounds) return 0;
  if (reachedEnd) return 1;
  return countPaths(i + 1, j) + countPaths(i, j + 1);
}

πŸ”— LeetCode Practice:


3. Backtracking (Generate All)

  • When to Use: Generate all combinations or permutations

  • Trick: Modify state β†’ recurse β†’ backtrack

  • Base Case: Add temp list to result

javaCopyEditvoid backtrack(List<Integer> temp, int[] nums, boolean[] used) {
  if (temp.size() == nums.length) {
    result.add(new ArrayList<>(temp));
    return;
  }
  for (int i = 0; i < nums.length; i++) {
    if (used[i]) continue;
    used[i] = true;
    temp.add(nums[i]);
    backtrack(temp, nums, used);
    used[i] = false;
    temp.remove(temp.size() - 1);
  }
}

πŸ”— LeetCode Practice:


4. Boolean Path Exists

  • When to Use: "Can I reach target?", "Is path possible?"

  • Trick: Return true if any path works (||)

  • Base Case: Return true if target met or path valid

javaCopyEditboolean canReach(int[] nums, int i, int target) {
  if (i == nums.length) return target == 0;
  return canReach(nums, i+1, target - nums[i]) || canReach(nums, i+1, target);
}

πŸ”— LeetCode Practice:


5. Target Matching (Subset/K-Sum Style)

  • When to Use: Subset sum-style questions

  • Trick: recurse(i+1, target - nums[i])

  • Base Case: If target == 0 β†’ true

javaCopyEditboolean solve(int i, int target) {
  if (target == 0) return true;
  if (i >= nums.length || target < 0) return false;
  return solve(i+1, target - nums[i]) || solve(i+1, target);
}

πŸ”— LeetCode Practice:


6. Recursion with Extra State (DP-ready)

  • When to Use: State changes beyond index (e.g., previous value, count)

  • Trick: Add extra param & memoize

  • Base Case: i out of bounds or limit reached

javaCopyEditint solve(int i, int prev) {
  if (i == nums.length) return 0;
  int take = 0;
  if (nums[i] > prev) {
    take = 1 + solve(i + 1, nums[i]);
  }
  int notTake = solve(i + 1, prev);
  return Math.max(take, notTake);
}

πŸ”— LeetCode Practice:


7. Build & Recurse (Constructive Recursion)

  • When to Use: Word break, expressions, palindromes

  • Trick: Build prefix β†’ validate β†’ recurse remainder

  • Base Case: If string fully processed

javaCopyEditvoid solve(String s, int idx, String path) {
  if (idx == s.length()) {
    result.add(path);
    return;
  }
  for (int i = idx+1; i <= s.length(); i++) {
    String part = s.substring(idx, i);
    if (isValid(part)) {
      solve(s, i, path + part + " ");
    }
  }
}

πŸ”— LeetCode Practice:


8. Combinatorial Constraints

  • When to Use: Placing items with rules (N-Queens, Sudoku)

  • Trick: Loop over choices β†’ check validity β†’ recurse

  • Base Case: All placed

javaCopyEditfor (int col = 0; col < n; col++) {
  if (isSafe(row, col)) {
    placeQueen(row, col);
    recurse(row + 1);
    removeQueen(row, col);
  }
}

πŸ”— LeetCode Practice:


9. Min/Max Path Recursion

  • When to Use: Grid/path questions for min or max

  • Trick: Return val + min(down, right)

  • Base Case: At bottom/right-most cell

javaCopyEditint solve(int i, int j) {
  if (i == m-1 && j == n-1) return grid[i][j];
  int down = solve(i+1, j);
  int right = solve(i, j+1);
  return grid[i][j] + Math.min(down, right);
}

πŸ”— LeetCode Practice:


10. DFS-style Recursive Exploration

  • When to Use: Matrix or graph traversal (flood fill, island count)

  • Trick: Visit β†’ Recurse on neighbors β†’ Unvisit (if needed)

  • Base Case: Out of bounds or already visited

javaCopyEditvoid dfs(int i, int j, boolean[][] vis) {
  if (invalid(i, j, vis)) return;
  vis[i][j] = true;
  for (int[] d : directions) {
    dfs(i + d[0], j + d[1], vis);
  }
}

πŸ”— LeetCode Practice:

Let’s Connect!

If you found this helpful, share it with your peers.
Connect with me on LinkedIn for more resources, DSA guides, and community discussions.

2
Subscribe to my newsletter

Read articles from Bama Charan Chhandogi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bama Charan Chhandogi
Bama Charan Chhandogi

I am a Dev from Kolkata who loves to write about technology.