Backtracking Techniques & Patterns 
Advanced techniques, optimization strategies, and detailed patterns for mastering backtracking algorithms.
📋 Table of Contents 
- Core Techniques
 - Optimization Strategies
 - Pattern Recognition
 - Implementation Strategies
 - Advanced Applications
 
Core Techniques 
1. State Representation 
Explicit State Tracking 
typescript
interface BacktrackState {
    path: number[];
    used: boolean[];
    currentSum: number;
    level: number;
}
function backtrack(state: BacktrackState): void {
    if (isGoal(state)) {
        recordSolution(state.path);
        return;
    }
    
    for (const choice of getValidChoices(state)) {
        const newState = makeChoice(state, choice);
        backtrack(newState);
    }
}Implicit State (Function Parameters) 
typescript
function backtrack(
    path: number[],
    used: boolean[],
    currentSum: number,
    level: number
): void {
    if (isGoal(path, currentSum, level)) {
        recordSolution(path);
        return;
    }
    
    for (const choice of getValidChoices(used, currentSum)) {
        path.push(choice);
        used[choice] = true;
        backtrack(path, used, currentSum + choice, level + 1);
        path.pop();
        used[choice] = false;
    }
}2. Choice Generation Strategies 
Index-Based Choices (Combinations) 
typescript
function generateCombinations(nums: number[], k: number): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    
    function backtrack(start: number): void {
        if (path.length === k) {
            result.push([...path]);
            return;
        }
        
        // Generate choices from start index onwards
        for (let i = start; i < nums.length; i++) {
            path.push(nums[i]);
            backtrack(i + 1); // Avoid duplicates by starting from i+1
            path.pop();
        }
    }
    
    backtrack(0);
    return result;
}Availability-Based Choices (Permutations) 
typescript
function generatePermutations(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;
        }
        
        // Generate choices from available elements
        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;
}Constraint-Based Choices (N-Queens) 
typescript
function solveNQueens(n: number): string[][] {
    const result: string[][] = [];
    const cols: Set<number> = new Set();
    const diag1: Set<number> = new Set(); // row - col
    const diag2: Set<number> = new Set(); // row + col
    const board: string[] = Array(n).fill('.'.repeat(n));
    
    function backtrack(row: number): void {
        if (row === n) {
            result.push([...board]);
            return;
        }
        
        // Generate valid column choices for current row
        for (let col = 0; col < n; col++) {
            if (!cols.has(col) && 
                !diag1.has(row - col) && 
                !diag2.has(row + col)) {
                
                // Make choice
                cols.add(col);
                diag1.add(row - col);
                diag2.add(row + col);
                board[row] = '.'.repeat(col) + 'Q' + '.'.repeat(n - col - 1);
                
                backtrack(row + 1);
                
                // Undo choice
                cols.delete(col);
                diag1.delete(row - col);
                diag2.delete(row + col);
                board[row] = '.'.repeat(n);
            }
        }
    }
    
    backtrack(0);
    return result;
}3. Base Case Patterns 
Length-Based Termination 
typescript
function backtrack(path: any[]): void {
    if (path.length === targetLength) {
        result.push([...path]);
        return;
    }
    // ... generate choices
}Index-Based Termination 
typescript
function backtrack(index: number): void {
    if (index === nums.length) {
        result.push([...path]);
        return;
    }
    // ... generate choices
}Condition-Based Termination 
typescript
function backtrack(currentSum: number, target: number): void {
    if (currentSum === target) {
        result.push([...path]);
        return;
    }
    if (currentSum > target) {
        return; // Invalid path, backtrack
    }
    // ... generate choices
}Optimization Strategies 
1. Pruning Techniques 
Bound-Based Pruning 
typescript
function combinationSum(candidates: number[], target: number): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    
    // Sort for better pruning
    candidates.sort((a, b) => a - b);
    
    function backtrack(start: number, currentSum: number): void {
        if (currentSum === target) {
            result.push([...path]);
            return;
        }
        
        for (let i = start; i < candidates.length; i++) {
            // Pruning: if current candidate exceeds remaining target,
            // all subsequent candidates will also exceed (due to sorting)
            if (currentSum + candidates[i] > target) {
                break; // Prune entire subtree
            }
            
            path.push(candidates[i]);
            backtrack(i, currentSum + candidates[i]);
            path.pop();
        }
    }
    
    backtrack(0, 0);
    return result;
}Feasibility Pruning 
typescript
function subsetSum(nums: number[], target: number): boolean {
    nums.sort((a, b) => a - b);
    
    function backtrack(index: number, currentSum: number): boolean {
        if (currentSum === target) return true;
        if (index >= nums.length || currentSum > target) return false;
        
        // Pruning: check if remaining elements can reach target
        const remainingSum = nums.slice(index).reduce((sum, num) => sum + num, 0);
        if (currentSum + remainingSum < target) {
            return false; // Cannot reach target even with all remaining elements
        }
        
        // Try including current element
        if (backtrack(index + 1, currentSum + nums[index])) {
            return true;
        }
        
        // Try excluding current element
        return backtrack(index + 1, currentSum);
    }
    
    return backtrack(0, 0);
}2. Duplicate Handling 
Skip Duplicates in Combinations 
typescript
function combinationSum2(candidates: number[], target: number): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    
    candidates.sort((a, b) => a - b);
    
    function backtrack(start: number, currentSum: number): void {
        if (currentSum === target) {
            result.push([...path]);
            return;
        }
        
        for (let i = start; i < candidates.length; i++) {
            // Skip duplicates: only use first occurrence at each level
            if (i > start && candidates[i] === candidates[i - 1]) {
                continue;
            }
            
            if (currentSum + candidates[i] > target) break;
            
            path.push(candidates[i]);
            backtrack(i + 1, currentSum + candidates[i]);
            path.pop();
        }
    }
    
    backtrack(0, 0);
    return result;
}Skip Duplicates in Permutations 
typescript
function permuteUnique(nums: number[]): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    const used: boolean[] = new Array(nums.length).fill(false);
    
    nums.sort((a, b) => a - b);
    
    function backtrack(): void {
        if (path.length === nums.length) {
            result.push([...path]);
            return;
        }
        
        for (let i = 0; i < nums.length; i++) {
            if (used[i]) continue;
            
            // Skip duplicates: only use if previous duplicate is used
            if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {
                continue;
            }
            
            path.push(nums[i]);
            used[i] = true;
            backtrack();
            path.pop();
            used[i] = false;
        }
    }
    
    backtrack();
    return result;
}3. Memoization for Overlapping Subproblems 
State-Based Memoization 
typescript
function wordBreak2(s: string, wordDict: string[]): string[] {
    const wordSet = new Set(wordDict);
    const memo = new Map<number, string[]>();
    
    function backtrack(start: number): string[] {
        if (start === s.length) {
            return [''];
        }
        
        if (memo.has(start)) {
            return memo.get(start)!;
        }
        
        const result: string[] = [];
        
        for (let end = start + 1; end <= s.length; end++) {
            const word = s.substring(start, end);
            
            if (wordSet.has(word)) {
                const suffixes = backtrack(end);
                for (const suffix of suffixes) {
                    result.push(word + (suffix ? ' ' + suffix : ''));
                }
            }
        }
        
        memo.set(start, result);
        return result;
    }
    
    return backtrack(0);
}4. Constraint Propagation 
Forward Checking 
typescript
interface SudokuState {
    board: number[][];
    rowSets: Set<number>[];
    colSets: Set<number>[];
    boxSets: Set<number>[];
}
function solveSudoku(board: string[][]): void {
    const state: SudokuState = initializeState(board);
    
    function backtrack(): boolean {
        const emptyCell = findEmptyCell(state.board);
        if (!emptyCell) return true; // Solved
        
        const [row, col] = emptyCell;
        const boxIndex = Math.floor(row / 3) * 3 + Math.floor(col / 3);
        
        // Generate valid choices using constraint propagation
        for (let num = 1; num <= 9; num++) {
            if (!state.rowSets[row].has(num) &&
                !state.colSets[col].has(num) &&
                !state.boxSets[boxIndex].has(num)) {
                
                // Make choice and update constraints
                state.board[row][col] = num;
                state.rowSets[row].add(num);
                state.colSets[col].add(num);
                state.boxSets[boxIndex].add(num);
                
                if (backtrack()) return true;
                
                // Undo choice and constraints
                state.board[row][col] = 0;
                state.rowSets[row].delete(num);
                state.colSets[col].delete(num);
                state.boxSets[boxIndex].delete(num);
            }
        }
        
        return false;
    }
    
    backtrack();
}Pattern Recognition 
1. Decision Tree Patterns 
Binary Choice Pattern (Include/Exclude) 
typescript
// Problem: Generate all subsets
function subsets(nums: number[]): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    
    function backtrack(index: number): void {
        if (index === nums.length) {
            result.push([...path]);
            return;
        }
        
        // Choice 1: Exclude current element
        backtrack(index + 1);
        
        // Choice 2: Include current element
        path.push(nums[index]);
        backtrack(index + 1);
        path.pop();
    }
    
    backtrack(0);
    return result;
}Multi-Choice Pattern (Multiple Options) 
typescript
// Problem: Letter combinations of phone number
function letterCombinations(digits: string): string[] {
    if (!digits) return [];
    
    const phoneMap: { [key: string]: string } = {
        '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
        '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
    };
    
    const result: string[] = [];
    const path: string[] = [];
    
    function backtrack(index: number): void {
        if (index === digits.length) {
            result.push(path.join(''));
            return;
        }
        
        const letters = phoneMap[digits[index]];
        for (const letter of letters) {
            path.push(letter);
            backtrack(index + 1);
            path.pop();
        }
    }
    
    backtrack(0);
    return result;
}2. Grid Traversal Patterns 
Path Finding with Backtracking 
typescript
function exist(board: string[][], word: string): boolean {
    const rows = board.length;
    const cols = board[0].length;
    const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]];
    
    function backtrack(row: number, col: number, index: number): boolean {
        if (index === word.length) return true;
        
        if (row < 0 || row >= rows || col < 0 || col >= cols ||
            board[row][col] !== word[index] || board[row][col] === '#') {
            return false;
        }
        
        // Mark as visited
        const temp = board[row][col];
        board[row][col] = '#';
        
        // Try all four directions
        for (const [dr, dc] of directions) {
            if (backtrack(row + dr, col + dc, index + 1)) {
                board[row][col] = temp; // Restore before returning
                return true;
            }
        }
        
        // Restore and backtrack
        board[row][col] = temp;
        return false;
    }
    
    // Try starting from each cell
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (backtrack(i, j, 0)) return true;
        }
    }
    
    return false;
}3. Recursive Structure Patterns 
Tree/Graph Traversal 
typescript
interface TreeNode {
    val: number;
    left: TreeNode | null;
    right: TreeNode | null;
}
function pathSum(root: TreeNode | null, targetSum: number): number[][] {
    const result: number[][] = [];
    const path: number[] = [];
    
    function backtrack(node: TreeNode | null, currentSum: number): void {
        if (!node) return;
        
        // Make choice: include current node
        path.push(node.val);
        currentSum += node.val;
        
        // Check if leaf node and target reached
        if (!node.left && !node.right && currentSum === targetSum) {
            result.push([...path]);
        }
        
        // Recursively explore children
        backtrack(node.left, currentSum);
        backtrack(node.right, currentSum);
        
        // Undo choice
        path.pop();
    }
    
    backtrack(root, 0);
    return result;
}Implementation Strategies 
1. Iterative Backtracking 
Stack-Based Implementation 
typescript
function iterativeBacktrack(nums: number[]): number[][] {
    const result: number[][] = [];
    const stack: { path: number[], index: number }[] = [{ path: [], index: 0 }];
    
    while (stack.length > 0) {
        const { path, index } = stack.pop()!;
        
        if (index === nums.length) {
            result.push([...path]);
            continue;
        }
        
        // Add choices to stack (in reverse order for correct traversal)
        stack.push({ path: [...path, nums[index]], index: index + 1 }); // Include
        stack.push({ path: [...path], index: index + 1 }); // Exclude
    }
    
    return result;
}2. Class-Based Organization 
Backtracking Solver Template 
typescript
abstract class BacktrackingSolver<T, R> {
    protected result: R[] = [];
    
    public solve(input: T): R[] {
        this.result = [];
        this.backtrack(this.getInitialState(input));
        return this.result;
    }
    
    protected abstract getInitialState(input: T): any;
    protected abstract isGoalState(state: any): boolean;
    protected abstract getChoices(state: any): any[];
    protected abstract isValidChoice(choice: any, state: any): boolean;
    protected abstract makeChoice(state: any, choice: any): any;
    protected abstract undoChoice(state: any, choice: any): void;
    protected abstract recordSolution(state: any): void;
    
    private backtrack(state: any): void {
        if (this.isGoalState(state)) {
            this.recordSolution(state);
            return;
        }
        
        for (const choice of this.getChoices(state)) {
            if (this.isValidChoice(choice, state)) {
                const newState = this.makeChoice(state, choice);
                this.backtrack(newState);
                this.undoChoice(state, choice);
            }
        }
    }
}
// Example implementation
class PermutationSolver extends BacktrackingSolver<number[], number[]> {
    protected getInitialState(nums: number[]) {
        return { nums, path: [], used: new Array(nums.length).fill(false) };
    }
    
    protected isGoalState(state: any): boolean {
        return state.path.length === state.nums.length;
    }
    
    protected getChoices(state: any): number[] {
        return state.nums.map((_: any, i: number) => i);
    }
    
    protected isValidChoice(choice: number, state: any): boolean {
        return !state.used[choice];
    }
    
    protected makeChoice(state: any, choice: number): any {
        state.path.push(state.nums[choice]);
        state.used[choice] = true;
        return state;
    }
    
    protected undoChoice(state: any, choice: number): void {
        state.path.pop();
        state.used[choice] = false;
    }
    
    protected recordSolution(state: any): void {
        this.result.push([...state.path]);
    }
}Advanced Applications 
1. Constraint Satisfaction with AC-3 
Arc Consistency for Sudoku 
typescript
class SudokuSolver {
    private board: number[][];
    private domains: Set<number>[][];
    
    constructor(board: string[][]) {
        this.board = board.map(row => row.map(cell => cell === '.' ? 0 : parseInt(cell)));
        this.initializeDomains();
        this.applyAC3();
    }
    
    private initializeDomains(): void {
        this.domains = Array(9).fill(null).map(() => 
            Array(9).fill(null).map(() => new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]))
        );
        
        // Remove impossible values based on initial state
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                if (this.board[i][j] !== 0) {
                    this.domains[i][j] = new Set([this.board[i][j]]);
                    this.propagateConstraints(i, j, this.board[i][j]);
                }
            }
        }
    }
    
    private applyAC3(): boolean {
        const queue: [number, number, number, number][] = [];
        
        // Initialize queue with all arcs
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                for (const [ni, nj] of this.getNeighbors(i, j)) {
                    queue.push([i, j, ni, nj]);
                }
            }
        }
        
        while (queue.length > 0) {
            const [i, j, ni, nj] = queue.shift()!;
            
            if (this.revise(i, j, ni, nj)) {
                if (this.domains[i][j].size === 0) {
                    return false; // Inconsistent
                }
                
                // Add affected arcs back to queue
                for (const [xi, xj] of this.getNeighbors(i, j)) {
                    if (xi !== ni || xj !== nj) {
                        queue.push([xi, xj, i, j]);
                    }
                }
            }
        }
        
        return true;
    }
    
    private revise(i: number, j: number, ni: number, nj: number): boolean {
        let revised = false;
        
        for (const value of Array.from(this.domains[i][j])) {
            if (this.domains[ni][nj].size === 1 && this.domains[ni][nj].has(value)) {
                this.domains[i][j].delete(value);
                revised = true;
            }
        }
        
        return revised;
    }
    
    private getNeighbors(row: number, col: number): [number, number][] {
        const neighbors: [number, number][] = [];
        
        // Row neighbors
        for (let j = 0; j < 9; j++) {
            if (j !== col) neighbors.push([row, j]);
        }
        
        // Column neighbors
        for (let i = 0; i < 9; i++) {
            if (i !== row) neighbors.push([i, col]);
        }
        
        // Box neighbors
        const boxRow = Math.floor(row / 3) * 3;
        const boxCol = Math.floor(col / 3) * 3;
        
        for (let i = boxRow; i < boxRow + 3; i++) {
            for (let j = boxCol; j < boxCol + 3; j++) {
                if (i !== row || j !== col) {
                    neighbors.push([i, j]);
                }
            }
        }
        
        return neighbors;
    }
}2. Parallel Backtracking 
Multi-threaded Search 
typescript
class ParallelBacktrackingSolver {
    private workers: Worker[] = [];
    private readonly numWorkers: number;
    
    constructor(numWorkers: number = navigator.hardwareConcurrency || 4) {
        this.numWorkers = numWorkers;
    }
    
    async solveParallel<T>(
        problem: T,
        splitStrategy: (problem: T, numParts: number) => T[]
    ): Promise<any[]> {
        const subProblems = splitStrategy(problem, this.numWorkers);
        
        const promises = subProblems.map((subProblem, index) => 
            this.solveInWorker(subProblem, index)
        );
        
        const results = await Promise.all(promises);
        return results.flat();
    }
    
    private async solveInWorker(subProblem: any, workerIndex: number): Promise<any[]> {
        return new Promise((resolve, reject) => {
            const worker = new Worker('./backtracking-worker.js');
            
            worker.postMessage({ problem: subProblem, workerIndex });
            
            worker.onmessage = (event) => {
                resolve(event.data.result);
                worker.terminate();
            };
            
            worker.onerror = (error) => {
                reject(error);
                worker.terminate();
            };
        });
    }
}This comprehensive technique guide covers advanced backtracking strategies, optimization methods, and implementation patterns that can be applied to solve complex computational problems efficiently.