Skip to content

Backtracking Algorithms

A comprehensive guide to mastering backtracking algorithms with TypeScript implementations, patterns, and real-world applications.

📚 Table of Contents

What is Backtracking?

Backtracking is an algorithmic paradigm that considers searching every possible combination in order to solve computational problems. It builds solutions incrementally and abandons candidates ("backtracks") when they cannot lead to a valid solution.

Key Characteristics

  • Incremental Construction: Build solution step by step
  • Constraint Checking: Validate each step against problem constraints
  • Backtrack on Failure: Undo choices that lead to invalid states
  • Exhaustive Search: Explore all possible paths (with pruning)

When to Use Backtracking

Good for:

  • Finding all possible solutions
  • Constraint satisfaction problems
  • Combinatorial optimization
  • Decision problems with multiple valid paths

Not optimal for:

  • Problems with optimal substructure (use DP instead)
  • Simple optimization problems
  • Problems requiring specific algorithmic approaches (greedy, divide & conquer)

Core Concepts

1. State Space Tree

Every backtracking algorithm explores a state space tree where:
- Root: Initial state
- Nodes: Partial solutions
- Leaves: Complete solutions or dead ends
- Edges: Choices/decisions

2. Three Essential Components

a) Choice

What options are available at each step?

typescript
for (const choice of availableChoices) {
    // Explore this choice
}

b) Constraint

Is this choice valid?

typescript
if (isValid(choice, currentState)) {
    // Proceed with choice
}

c) Goal

Have we reached a complete solution?

typescript
if (isComplete(currentState)) {
    // Found solution
    result.push(currentState);
    return;
}

3. Universal Template

typescript
function backtrack(state: State, choices: Choice[]): void {
    // Base case: check if solution is complete
    if (isGoalState(state)) {
        recordSolution(state);
        return;
    }
    
    // Try each available choice
    for (const choice of getValidChoices(state, choices)) {
        // Make choice (modify state)
        makeChoice(state, choice);
        
        // Recursively solve subproblem
        backtrack(state, getNextChoices(state, choices));
        
        // Undo choice (backtrack)
        undoChoice(state, choice);
    }
}

Problem Classification

1. Subset/Combination Problems

Generate all possible combinations of elements.

Examples: Subsets, Combination Sum, Power Set

typescript
// Template: Include/Exclude pattern
function subsets(nums: number[]): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    
    function backtrack(index: number): void {
        if (index === nums.length) {
            result.push([...path]);
            return;
        }
        
        // Exclude current element
        backtrack(index + 1);
        
        // Include current element
        path.push(nums[index]);
        backtrack(index + 1);
        path.pop();
    }
    
    backtrack(0);
    return result;
}

2. Permutation Problems

Generate all possible arrangements of elements.

Examples: Permutations, N-Queens, Anagram Generation

typescript
// Template: Choose from remaining elements
function permute(nums: number[]): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    const used: boolean[] = new Array(nums.length).fill(false);
    
    function backtrack(): void {
        if (path.length === nums.length) {
            result.push([...path]);
            return;
        }
        
        for (let i = 0; i < nums.length; i++) {
            if (!used[i]) {
                path.push(nums[i]);
                used[i] = true;
                backtrack();
                path.pop();
                used[i] = false;
            }
        }
    }
    
    backtrack();
    return result;
}

3. Constraint Satisfaction Problems

Find solutions that satisfy given constraints.

Examples: Sudoku, N-Queens, Graph Coloring

typescript
// Template: Place elements following rules
function solveNQueens(n: number): string[][] {
    const result: string[][] = [];
    const board: number[] = new Array(n).fill(-1); // board[i] = column of queen in row i
    
    function isValid(row: number, col: number): boolean {
        for (let i = 0; i < row; i++) {
            if (board[i] === col || 
                Math.abs(board[i] - col) === Math.abs(i - row)) {
                return false;
            }
        }
        return true;
    }
    
    function backtrack(row: number): void {
        if (row === n) {
            // Convert to string representation
            const solution = Array(n).fill(0).map((_, r) => 
                '.'.repeat(board[r]) + 'Q' + '.'.repeat(n - board[r] - 1)
            );
            result.push(solution);
            return;
        }
        
        for (let col = 0; col < n; col++) {
            if (isValid(row, col)) {
                board[row] = col;
                backtrack(row + 1);
                board[row] = -1;
            }
        }
    }
    
    backtrack(0);
    return result;
}

4. Partition Problems

Divide input into valid segments.

Examples: Palindrome Partitioning, Word Break, IP Address Restoration

typescript
// Template: Try all valid partitions
function partition(s: string): string[][] {
    const result: string[][] = [];
    const path: string[] = [];
    
    function isPalindrome(str: string): boolean {
        let left = 0, right = str.length - 1;
        while (left < right) {
            if (str[left] !== str[right]) return false;
            left++;
            right--;
        }
        return true;
    }
    
    function backtrack(start: number): void {
        if (start === s.length) {
            result.push([...path]);
            return;
        }
        
        for (let end = start; end < s.length; end++) {
            const substring = s.slice(start, end + 1);
            if (isPalindrome(substring)) {
                path.push(substring);
                backtrack(end + 1);
                path.pop();
            }
        }
    }
    
    backtrack(0);
    return result;
}

Templates & Patterns

1. Basic Template

typescript
function backtrack(path: any[], choices: any[]): void {
    if (baseCase) {
        result.push([...path]);
        return;
    }
    
    for (const choice of choices) {
        if (isValid(choice)) {
            path.push(choice);
            backtrack(path, newChoices);
            path.pop();
        }
    }
}

2. With Start Index (Combinations)

typescript
function backtrack(start: number, path: any[]): void {
    if (baseCase) {
        result.push([...path]);
        return;
    }
    
    for (let i = start; i < choices.length; i++) {
        path.push(choices[i]);
        backtrack(i + 1, path); // i+1 to avoid duplicates
        path.pop();
    }
}

3. With Used Array (Permutations)

typescript
function backtrack(path: any[], used: boolean[]): void {
    if (path.length === target) {
        result.push([...path]);
        return;
    }
    
    for (let i = 0; i < choices.length; i++) {
        if (!used[i]) {
            path.push(choices[i]);
            used[i] = true;
            backtrack(path, used);
            path.pop();
            used[i] = false;
        }
    }
}

Problem Implementations

This directory contains the following problem solutions:

TypeScript Solutions

Documentation

Subdirectories

  • N-Queens - N-Queens problem variations and solutions

Common Problems

Easy Level

  • Subsets - Generate all possible subsets
  • Letter Combinations - Phone number to letter combinations
  • Generate Parentheses - All combinations of well-formed parentheses

Medium Level

  • Combination Sum - Find all combinations that sum to target
  • Permutations - Generate all permutations of array
  • Word Search - Find word in 2D board
  • Palindrome Partitioning - Partition string into palindromes

Hard Level

  • N-Queens - Place N queens on N×N chessboard
  • Sudoku Solver - Solve 9×9 Sudoku puzzle
  • Word Search II - Find multiple words in 2D board
  • Expression Add Operators - Insert operators to reach target

Advanced Techniques

1. Pruning

Eliminate branches early to reduce search space.

typescript
function backtrack(sum: number, target: number): void {
    if (sum > target) return; // Pruning: early termination
    if (sum === target) {
        result.push([...path]);
        return;
    }
    // ... rest of logic
}

2. Memoization

Cache results to avoid redundant computations.

typescript
const memo = new Map<string, any>();

function backtrack(state: string): any {
    if (memo.has(state)) {
        return memo.get(state);
    }
    
    // ... compute result
    memo.set(state, result);
    return result;
}

3. Constraint Propagation

Use problem constraints to limit choices.

typescript
function getValidChoices(state: State): Choice[] {
    return allChoices.filter(choice => 
        satisfiesConstraint1(choice, state) &&
        satisfiesConstraint2(choice, state)
    );
}

Time & Space Complexity

Typical Complexities

Problem TypeTime ComplexitySpace Complexity
SubsetsO(2^n)O(n)
PermutationsO(n!)O(n)
CombinationsO(C(n,k) * k)O(k)
N-QueensO(n!)O(n)
SudokuO(9^(n*n))O(n*n)

Analysis Tips

  • State Space Size: How many possible states exist?
  • Branching Factor: Average number of choices per state
  • Depth: Maximum recursion depth
  • Pruning Effect: How much search space can be eliminated?

Best Practices

1. Code Organization

typescript
class BacktrackingSolver {
    private result: any[] = [];
    
    solve(input: any): any[] {
        this.result = [];
        this.backtrack(/* initial state */);
        return this.result;
    }
    
    private backtrack(state: State): void {
        // Implementation
    }
    
    private isValid(choice: Choice, state: State): boolean {
        // Validation logic
    }
}

2. Debug Tips

  • Add logging to track recursive calls
  • Visualize the search tree
  • Test with small inputs first
  • Verify constraint checking logic

3. Optimization Guidelines

  • Sort input when possible for better pruning
  • Use bit manipulation for state representation
  • Implement iterative versions for very deep recursion
  • Consider parallel processing for independent branches

Comparison with Dynamic Programming

AspectBacktrackingDynamic Programming
ApproachExhaustive search with backtrackingOptimal substructure + memoization
Use CaseFind all solutions / decision problemsOptimization problems
Time ComplexityOften exponentialUsually polynomial
Space UsageRecursion stackMemoization table
When to UseMultiple valid solutions neededSingle optimal solution needed

Resources

Further Reading

Practice Platforms

  • LeetCode Backtracking Problems
  • HackerRank Algorithm Challenges
  • Codeforces Combinatorial Problems

Books & Papers

  • "Introduction to Algorithms" - CLRS
  • "Algorithm Design Manual" - Skiena
  • "Combinatorial Algorithms" - Knuth

Last updated: July 2025

Software Engineer Interview Preparation