# Backtracking Algorithm Approach: Core Concepts and Common Patterns for Problem Solving

> Learn about the backtracking algorithm approach, its core concepts, and common patterns. Discover how this depth-first search technique prunes invalid paths to efficiently solve problems.

- Repository: [lucifer/leetcode](https://github.com/azl397985856/leetcode)
- Tags: deep-dive
- Published: 2026-03-06

---

**Backtracking is a depth-first search technique that incrementally builds solution candidates and abandons invalid paths through "pruning," following a systematic "choose-explore-undo" cycle implemented across the azl397985856/leetcode repository.**

The backtracking algorithm approach serves as a fundamental strategy for solving constraint satisfaction problems by exploring a **state tree** of decisions. According to the repository's detailed guide at [`thinkings/backtrack.en.md`](https://github.com/azl397985856/leetcode/blob/main/thinkings/backtrack.en.md), this method combines exhaustive enumeration with intelligent pruning to efficiently search solution spaces without exploring impossible branches.

## Core Mechanics of the Backtracking Algorithm Approach

Backtracking operates as a refined **depth-first search (DFS)** that constructs solutions incrementally. The algorithm visualizes the problem as a decision tree where each node represents a partial solution, traversing paths recursively while eliminating dead ends.

The approach follows five essential steps:

1. **Construct a state tree** – Each level represents a new decision, such as selecting the next element or movement direction.
2. **Traverse recursively** – Use DFS to explore each branch of the decision tree.
3. **Prune invalid branches** – Abandon paths that violate constraints or cannot possibly lead to a valid solution.
4. **Record valid solutions** – Capture complete solutions when terminal nodes satisfy target conditions.
5. **Undo decisions** – Restore the previous state before exploring alternative branches.

This "try, fail, and back up" methodology ensures the algorithm only commits resources to viable paths, making it essential for **combinatorial search problems** where brute force would be computationally infeasible.

## Common Patterns in Backtracking Implementation

The azl397985856/leetcode repository identifies five recurring patterns that demonstrate how to apply the backtracking algorithm approach to different problem categories.

### Subset and Combination Generation

This pattern addresses problems like "Generate all subsets" or "Combination Sum" by iterating through elements and making binary **include-or-exclude** decisions. The implementation uses a `start` index to prevent duplicate combinations, recursively building subsets while avoiding reprocessing earlier elements.

**Key file reference**: The pattern is fully documented in [`problems/78.subsets.md`](https://github.com/azl397985856/leetcode/blob/main/problems/78.subsets.md), which demonstrates how to generate all possible subsets of an array without repetition.

### Permutation and Full Arrangement

For problems requiring all possible orderings of elements (such as LeetCode 46), this pattern tracks used elements through a **visited array** or set. At each recursive depth, the algorithm selects any unused element, marks it as visited, explores deeper levels, then unmarks it during backtracking to allow reuse in other branches.

**Key file reference**: See [`problems/46.permutations.md`](https://github.com/azl397985856/leetcode/blob/main/problems/46.permutations.md) for the complete implementation using a `visited` boolean array to manage element selection.

### Path-Finding in Grids and Boards

This pattern applies to spatial search problems like "Word Search," "Sudoku," and "N-Queens." The algorithm moves in four or eight directional steps across a matrix, maintaining a **visited matrix** to track current path occupancy. When a move proves illegal or completes the target word/path, the algorithm backtracks by clearing the visited flag and tries alternative directions.

**Key file reference**: The grid traversal technique with visited-matrix handling appears in [`problems/79.word-search.md`](https://github.com/azl397985856/leetcode/blob/main/problems/79.word-search.md).

### Partition and Split Problems

Problems like "Palindrome Partitioning" or "Split Array into Fibonacci Sequence" require dividing input strings at every possible index. The algorithm recursively processes the suffix of each split, validating constraints (such as numeric limits or sequence ordering) before continuing deeper into the recursion.

**Key file reference**: Advanced pruning strategies for partition problems are detailed in the "Pruning" section of [`thinkings/backtrack.en.md`](https://github.com/azl397985856/leetcode/blob/main/thinkings/backtrack.en.md) (lines 64-70).

### State-Space Pruning Optimization

This meta-pattern enhances any backtracking implementation by adding **constraint checks before recursion**. Early validation eliminates large subtrees by checking conditions like sum overflow, leading zeros in numeric strings, or impossible remaining sums, significantly reducing the search space.

## Practical Code Examples from the Repository

### Subset Generation (LeetCode 78) – JavaScript

The following implementation from [`problems/78.subsets.md`](https://github.com/azl397985856/leetcode/blob/main/problems/78.subsets.md) demonstrates the include-or-exclude pattern using a start index to avoid duplicates:

```javascript
function subsets(nums) {
  const result = [];

  function backtrack(start, path) {
    result.push([...path]);                 // record current subset
    for (let i = start; i < nums.length; i++) {
      path.push(nums[i]);                   // choose
      backtrack(i + 1, path);              // explore
      path.pop();                           // undo
    }
  }

  backtrack(0, []);
  return result;
}

```

The `start` parameter ensures each recursive call only considers elements after the current index, preventing the generation of duplicate subsets like `[1,2]` and `[2,1]`.

### Permutation Generation (LeetCode 46) – Python

As implemented in [`problems/46.permutations.md`](https://github.com/azl397985856/leetcode/blob/main/problems/46.permutations.md), this solution uses a `visited` array to track selected elements:

```python
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []
        visited = [False] * len(nums)

        def dfs(path):
            if len(path) == len(nums):
                res.append(path[:])          # store a copy

                return
            for i in range(len(nums)):
                if visited[i]:
                    continue
                visited[i] = True
                path.append(nums[i])
                dfs(path)                    # go deeper

                path.pop()                   # undo

                visited[i] = False

        dfs([])
        return res

```

The algorithm marks `visited[i] = True` before recursion and resets it to `False` during backtracking, allowing the same element to participate in different positions across separate permutation branches.

### Word Search (LeetCode 79) – Java

This grid-based implementation from [`problems/79.word-search.md`](https://github.com/azl397985856/leetcode/blob/main/problems/79.word-search.md) demonstrates directional movement with state restoration:

```java
class Solution {
    private int m, n;
    private char[][] board;
    private String word;
    private boolean[][] visited;

    public boolean exist(char[][] board, String word) {
        this.board = board;
        this.word = word;
        m = board.length;
        n = board[0].length;
        visited = new boolean[m][n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (dfs(i, j, 0)) return true;
            }
        }
        return false;
    }

    private boolean dfs(int i, int j, int idx) {
        if (idx == word.length()) return true;
        if (i < 0 || i >= m || j < 0 || j >= n ||
            visited[i][j] || board[i][j] != word.charAt(idx))
            return false;

        visited[i][j] = true;
        boolean found = dfs(i+1, j, idx+1) ||
                        dfs(i-1, j, idx+1) ||
                        dfs(i, j+1, idx+1) ||
                        dfs(i, j-1, idx+1);
        visited[i][j] = false;   // undo
        return found;
    }
}

```

The `visited[i][j] = false` line constitutes the critical backtracking step, clearing the cell's occupied status before the algorithm explores alternative paths from previous decision points.

### Pruned Fibonacci Split – Python

This advanced example from the backtracking guide demonstrates aggressive state-space pruning:

```python
class Solution:
    def splitIntoFibonacci(self, S: str) -> List[int]:
        def backtrack(start, path):
            # prune: length constraint

            if len(path) > 2 and path[-1] != path[-2] + path[-3]:
                return []
            if start == len(S):
                return path if len(path) >= 3 else []

            cur = 0
            for i in range(start, len(S)):
                # prune: leading zero

                if i > start and S[start] == '0':
                    break
                cur = cur * 10 + int(S[i])
                # prune: overflow

                if cur > 2**31 - 1:
                    break
                path.append(cur)
                ans = backtrack(i + 1, path)
                if ans:
                    return ans
                path.pop()               # undo

            return []

        return backtrack(0, [])

```

The implementation applies three pruning layers: Fibonacci sequence validation, leading zero prevention, and integer overflow checking, eliminating invalid branches before expensive recursive calls.

## Summary

- The **backtracking algorithm approach** is a DFS-based method that builds solutions incrementally while abandoning invalid paths through pruning.
- Five common patterns emerge from the azl397985856/leetcode repository: **Subset Generation**, **Permutation Tracking**, **Grid Path-Finding**, **String Partitioning**, and **State-Space Pruning**.
- The universal "choose-explore-undo" template requires explicitly recording choices, recursing to deeper levels, and restoring state before exploring alternatives.
- Critical implementation files include [`thinkings/backtrack.en.md`](https://github.com/azl397985856/leetcode/blob/main/thinkings/backtrack.en.md) for theoretical foundations and problem-specific files like [`problems/78.subsets.md`](https://github.com/azl397985856/leetcode/blob/main/problems/78.subsets.md) and [`problems/46.permutations.md`](https://github.com/azl397985856/leetcode/blob/main/problems/46.permutations.md) for concrete examples.
- Effective pruning—checking constraints before recursion—is essential for optimizing backtracking performance on large input spaces.

## Frequently Asked Questions

### What distinguishes backtracking from standard recursion?

**Backtracking is a specialized form of recursion that systematically explores decision trees and explicitly undoes choices (restores state) after exploring each branch.** While standard recursion may simply divide problems into subproblems, backtracking specifically targets constraint satisfaction by pruning invalid paths and backtracking to previous decision points when branches fail, as demonstrated in the state restoration patterns found in [`problems/79.word-search.md`](https://github.com/azl397985856/leetcode/blob/main/problems/79.word-search.md).

### How do I choose between a visited array and a start index in backtracking?

**Use a visited array for permutation problems where element order matters but reuse is prohibited within a single path, and use a start index for combination/subset problems where element order does not matter.** The start index technique (shown in [`problems/78.subsets.md`](https://github.com/azl397985856/leetcode/blob/main/problems/78.subsets.md)) prevents generating duplicate combinations by only considering elements after the current position, while the visited array (used in [`problems/46.permutations.md`](https://github.com/azl397985856/leetcode/blob/main/problems/46.permutations.md)) tracks which specific elements are currently in the partial solution path.

### When should I apply pruning in a backtracking solution?

**Apply pruning immediately before making recursive calls to eliminate branches that violate constraints or cannot possibly lead to valid solutions.** According to [`thinkings/backtrack.en.md`](https://github.com/azl397985856/leetcode/blob/main/thinkings/backtrack.en.md), effective pruning checks include validating numeric ranges, detecting leading zeros, verifying sum conditions (as in [`problems/39.combination-sum.md`](https://github.com/azl397985856/leetcode/blob/main/problems/39.combination-sum.md)), and checking diagonal conflicts (in [`problems/52.N-Queens-II.md`](https://github.com/azl397985856/leetcode/blob/main/problems/52.N-Queens-II.md)), all of which prevent the algorithm from wasting time exploring doomed subtrees.

### Can backtracking solve problems with large input sizes?

**Backtracking is generally suited for problems with moderate input sizes (typically N ≤ 20-30) because its exponential time complexity makes it impractical for massive search spaces without aggressive pruning.** The approach remains viable for larger inputs only when combined with sophisticated pruning strategies or constraint propagation that significantly reduce the effective search tree, as illustrated by the optimized Fibonacci split implementation in the repository's backtracking guide.