Table of Contents
838 words
4 minutes
Backtracking Algorithms: From Permutations to N-Queens in TypeScript

Overview#

Backtracking is an enumeration method that performs a depth-first search on a decision tree. It tries a choice and continues searching downward; if the current path does not satisfy the conditions, it undoes the choice and returns to the previous level to try other branches.

Prerequisites

  • DFS: Backtracking is essentially a depth-first traversal of a decision tree
  • Recursion: Using the call stack to save the choice state at each level
  • Pruning: Skipping branches that cannot possibly produce an answer

Problem Definition#

Given a set of choice spaces, enumerate all solutions that satisfy the constraints, and avoid invalid branches during the search process.

ElementDescription
InputCandidate elements, target length, target sum, or board constraints
OutputAll paths, combinations, permutations, or layouts that satisfy the conditions
Key StateCurrent path, available range, used markers, constraint checking
Typical ProblemsFull permutations, combinations, subsets, combination sum, N-Queens

Core Principle: Step-by-Step Illustration#

Taking the full permutations of [1, 2, 3] as an example, each level selects a number that has not yet been used:

Empty path Choice 1 Choice 2 Choice 3 1,2 1,3 1,2,3 1,3,2

The core actions of backtracking can be broken down into three steps:

  1. Make a choice: Add a candidate element to the current path.
  2. Continue searching: Recurse into the next level of decision-making.
  3. Undo the choice: Restore the state so that the same level can try the next candidate.

If all paths are viewed as a tree, backtracking is “walking all the way down one path, then retreating to switch paths.”


Detailed Algorithm Steps#

Algorithm: Backtrack(path, choices)
Input: current path path, set of choices choices
Output: all solutions that satisfy the conditions
1. If path already satisfies the termination condition:
2. Copy path to the result set
3. Return
4. Iterate over each choice in choices:
5. If choice does not satisfy the constraints, skip
6. Add choice to path
7. Update the selection state
8. Backtrack(path, nextChoices)
9. Undo choice and state update

Complexity Analysis:

Problem TypeSearch ScaleDescription
PermutationsO(n × n!)n! permutations, each result copied in O(n)
CombinationsO(k × C(n,k))Only enumerates paths of length k
SubsetsO(n × 2^n)Each element is either chosen or not
N-QueensApproximately O(n!)After pruning, far less than brute-force n^n
Space ComplexityO(n) + outputRecursion depth and current path are O(n)

TypeScript Implementation#

1. Full Permutations#

function permute(nums: number[]): number[][] {
const result: number[][] = [];
const path: number[] = [];
const used = 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]) continue;
used[i] = true;
path.push(nums[i]);
backtrack();
path.pop();
used[i] = false;
}
}
backtrack();
return result;
}

2. Combinations#

function combine(n: number, k: number): number[][] {
const result: number[][] = [];
const path: number[] = [];
function backtrack(start: number): void {
if (path.length === k) {
result.push([...path]);
return;
}
for (let i = start; i <= n - (k - path.length) + 1; i++) {
path.push(i);
backtrack(i + 1);
path.pop();
}
}
backtrack(1);
return result;
}

3. Subsets#

function subsets(nums: number[]): number[][] {
const result: number[][] = [];
const path: number[] = [];
function backtrack(start: number): void {
result.push([...path]);
for (let i = start; i < nums.length; i++) {
path.push(nums[i]);
backtrack(i + 1);
path.pop();
}
}
backtrack(0);
return result;
}

4. N-Queens#

function solveNQueens(n: number): string[][] {
const result: string[][] = [];
const board = Array.from({ length: n }, () => new Array(n).fill('.'));
const cols = new Set<number>();
const diag1 = new Set<number>();
const diag2 = new Set<number>();
function backtrack(row: number): void {
if (row === n) {
result.push(board.map(line => line.join('')));
return;
}
for (let col = 0; col < n; col++) {
if (cols.has(col) || diag1.has(row - col) || diag2.has(row + col)) continue;
cols.add(col);
diag1.add(row - col);
diag2.add(row + col);
board[row][col] = 'Q';
backtrack(row + 1);
board[row][col] = '.';
cols.delete(col);
diag1.delete(row - col);
diag2.delete(row + col);
}
}
backtrack(0);
return result;
}

Engineering Optimization: Pruning and State Isolation#

The most error-prone aspect of backtracking code is state reuse. In engineering, optimization can be approached from three angles:

Optimization PointApproachValue
Pruning conditions upfrontFirst check whether remaining elements are sufficient and whether the current sum exceeds the limitReduces invalid recursion
Paired state modificationspush and pop, add and delete appear in pairsAvoids contaminating other branches
Copy resultsUse [...path] when collecting answersPrevents subsequent backtracking from modifying already-saved results

In N-Queens, using the three sets cols / diag1 / diag2 instead of scanning cell by cell reduces each validity check from O(n) to O(1).


Applications and Limitations#

Typical Applications#

  • Enumeration problems such as permutations, combinations, and subsets
  • Board search: N-Queens, Sudoku
  • Path search: maze paths, word search
  • Constraint satisfaction problems: parenthesis generation, phone number letter combinations

Limitations#

LimitationDescription
Large search spaceMost backtracking problems are inherently exponential
Prone to state contaminationForgetting to undo a choice leads to incorrect results
Limited recursion depthExcessive depth may trigger call stack limits

Summary#

Define path state Enumerate current choices Check constraints Make a choice Recurse to next level Undo choice

Key Takeaways:

  1. Backtracking = DFS + state selection + undoing choices.
  2. Permutations use used to control repeated use; combinations and subsets use start to control order.
  3. Pruning does not change the answer; it skips branches that cannot possibly produce an answer.
  4. When collecting results, you must copy the current path and cannot directly save a reference.
Backtracking Algorithms: From Permutations to N-Queens in TypeScript
https://www.hehonglei.com/technology/backtracking/
Author
HHLBlog
Published at
2026-04-26
License
CC BY-NC-SA 4.0