8 Queens Problem Backtracking - Algorithm Guide

A complete guide to solving the 8 queens problem using backtracking. Understand the isSafe function, recursive approach, time complexity O(n!), and optimizations like symmetry breaking and bit manipulation.

What is Backtracking?

Backtracking is a general algorithmic technique that incrementally builds candidates for a solution and abandons a candidate (backtracks) as soon as it determines that the candidate cannot be extended to a valid complete solution. It is a refined form of exhaustive search that prunes the search tree dramatically.

The core idea is simple: try to place a queen in a column, check whether the placement is safe, proceed to the next row if it is, and undo the placement (backtrack) if no safe column exists in the current row. This systematic trial-and-error approach is guaranteed to find all solutions — for the 8x8 board that is exactly 92 distinct solutions.

Backtracking is used throughout computer science: constraint satisfaction, Sudoku solvers, maze navigation, and the N queens problem algorithm in AI courses all rely on this technique. Understanding it through the 8 queens problem gives you a foundation applicable to hundreds of interview problems.

How Backtracking Solves 8 Queens

The 8 queens problem asks you to place 8 queens on an 8×8 chessboard so that no two queens attack each other — no shared row, column, or diagonal. The backtracking algorithm exploits one constraint immediately: since each row must contain exactly one queen, we can represent the entire board as a one-dimensional array where board[row] = col.

Here is the high-level pseudocode:

function solveNQueens(n):
    board = array of size n, initialized to -1
    backtrack(board, row=0, n)
    return all solutions

function backtrack(board, row, n):
    if row == n:
        record solution
        return
    for col from 0 to n-1:
        if isSafe(board, row, col):
            board[row] = col
            backtrack(board, row + 1, n)
            board[row] = -1   // undo (backtrack)

Each recursive call tries all columns for the current row, pruning branches early whenever a conflict is detected. For n=8 the algorithm explores far fewer than the theoretical 8^8 = 16,777,216 placements.

Step-by-Step Walkthrough

Let's trace the first few steps for an 8×8 board:

  1. Row 0: Try column 0. No queens placed yet, so it is always safe. Place queen at (0,0). Recurse to row 1.
  2. Row 1: Try column 0. Conflicts with queen at (0,0) — same column. Skip. Try column 1. Conflicts diagonally with (0,0). Skip. Try column 2. Safe — place queen at (1,2). Recurse to row 2.
  3. Row 2: Try columns 0–3, all conflict. Try column 4. Safe — place queen at (2,4). Continue…
  4. Eventually row 3 finds no safe column. Backtrack: remove queen from row 2, try column 5 instead of 4.
  5. This continues until all 92 solutions are found.

The search tree has depth 8, and pruning means most branches are cut well before reaching depth 8. The algorithm is said to be depth-first with constraint propagation — a pattern you will encounter again in AI search problems.

The isSafe Function

The isSafe(board, row, col) function is the heart of the algorithm. It must check three conditions for every already-placed queen in rows 0 to row-1:

  • Same column: board[i] === col
  • Same main diagonal (top-left to bottom-right): board[i] - col === i - row, which simplifies to board[i] - i === col - row
  • Same anti-diagonal (top-right to bottom-left): board[i] + i === col + row

Because we place exactly one queen per row and iterate from top to bottom, we never need to check for row conflicts — they are impossible by construction. This reduces each safety check to O(row) work, and the total work per node in the search tree is linear in the depth.

function isSafe(board, row, col) {
    for (let i = 0; i < row; i++) {
        // Same column check
        if (board[i] === col) return false;
        // Same diagonal check
        if (Math.abs(board[i] - col) === Math.abs(i - row)) return false;
    }
    return true;
}

Complete JavaScript Implementation

The following is a complete, runnable JavaScript implementation that finds all 92 solutions to the 8 queens problem. It works in Node.js and in the browser. You can also try the interactive puzzle to visualize the solutions.

/**
 * Solves the N-Queens problem using backtracking.
 * Returns an array of all solutions, where each solution is
 * an array of column indices (board[row] = col).
 */
function isSafe(board, row, col) {
    for (let i = 0; i < row; i++) {
        if (board[i] === col) return false;
        if (Math.abs(board[i] - col) === Math.abs(i - row)) return false;
    }
    return true;
}

function solveNQueens(n) {
    const solutions = [];
    const board = new Array(n).fill(-1);

    function backtrack(row) {
        if (row === n) {
            solutions.push([...board]);
            return;
        }
        for (let col = 0; col < n; col++) {
            if (isSafe(board, row, col)) {
                board[row] = col;
                backtrack(row + 1);
                board[row] = -1; // backtrack
            }
        }
    }

    backtrack(0);
    return solutions;
}

// Solve for 8 queens
const solutions = solveNQueens(8);
console.log(`Total solutions for 8-Queens: ${solutions.length}`); // 92

// Print the first solution as a board
function printBoard(solution) {
    const n = solution.length;
    for (let row = 0; row < n; row++) {
        let line = "";
        for (let col = 0; col < n; col++) {
            line += solution[row] === col ? "Q " : ". ";
        }
        console.log(line);
    }
    console.log("---");
}

printBoard(solutions[0]);

Time Complexity O(n!)

The worst-case time complexity of the backtracking approach is O(n!). Here's why:

  • In row 0, there are n choices.
  • In row 1, at most n-1 non-conflicting columns remain.
  • In row 2, at most n-2, and so on.
  • The product n × (n-1) × (n-2) × … × 1 = n! gives the factorial upper bound.

For n=8 this is 8! = 40,320 — far fewer than the 8^8 = 16.7 million brute-force permutations. In practice, with constraint checking, the algorithm explores far fewer nodes than the O(n!) bound suggests because many branches are pruned early.

For comparison, the brute-force O(n^n) approach would need to evaluate all 16.7 million cells. Backtracking reduces this to roughly 15,720 recursive calls for n=8 — a 1000x improvement.

Space Complexity O(n)

The space complexity of the backtracking algorithm is O(n), dominated by:

  • The board array of size n — O(n)
  • The recursion call stack, which has at most n frames active simultaneously — O(n)

This is a key advantage over approaches that store intermediate board states explicitly. By using a single mutable array and undoing changes on backtrack, we keep memory usage minimal regardless of the number of solutions found. Even for n=15, where there are 2,279,184 solutions, memory usage stays under a kilobyte during search.

Optimizations: Symmetry Breaking & Bit Manipulation

Two major optimizations can dramatically speed up the backtracking solver:

1. Symmetry Breaking

The 8×8 board has 8-fold symmetry (4 rotations × 2 reflections). By fixing the first queen in only the first 4 columns and then reflecting or rotating solutions, you can reduce the search space by up to 8×. This is especially useful when you only need distinct solutions rather than all placements.

2. Bit Manipulation

Instead of scanning all placed queens in isSafe, maintain three bitmasks:

  • cols — columns already occupied
  • diag1 — main diagonals occupied (shift left by 1 each row)
  • diag2 — anti-diagonals occupied (shift right by 1 each row)

The set of safe columns for the current row is ~(cols | diag1 | diag2) & fullMask. Extracting each candidate via bit = available & -available (lowest set bit trick) gives O(1) safety checks per candidate. This bit-manipulation approach can be 10–50× faster than the naive version for large n.

// Bit-manipulation based N-Queens solver
function solveNQueensBit(n) {
    let count = 0;
    const fullMask = (1 << n) - 1;

    function bt(cols, diag1, diag2) {
        if (cols === fullMask) { count++; return; }
        let available = fullMask & ~(cols | diag1 | diag2);
        while (available) {
            const bit = available & -available; // lowest set bit
            available &= available - 1;         // clear lowest set bit
            bt(cols | bit, (diag1 | bit) << 1, (diag2 | bit) >> 1);
        }
    }

    bt(0, 0, 0);
    return count;
}

console.log(solveNQueensBit(8)); // 92

Frequently Asked Questions

What is the time complexity of the 8 queens backtracking algorithm?

The worst-case time complexity is O(n!), which for n=8 is 40,320 operations. In practice, constraint pruning reduces the actual number of recursive calls to roughly 15,720 for n=8 — orders of magnitude fewer than the brute-force O(n^n) approach.

How does backtracking work in the 8 queens problem?

Backtracking places one queen per row, checking after each placement whether it conflicts with any previously placed queen. If a conflict is found or no safe column exists, it undoes (backtracks) the last placement and tries the next column. This systematic depth-first search guarantees finding all 92 solutions.

Is backtracking the fastest method for solving 8 queens?

For finding all 92 solutions, the bit-manipulation optimized backtracking is typically the fastest algorithmic approach, running in under a millisecond on modern hardware. Heuristic methods like min-conflicts can find a single solution faster but do not enumerate all solutions. For a comparison of all approaches, see the N Queens Algorithm guide.

How many recursive calls does the backtracking algorithm make for 8 queens?

The standard backtracking algorithm makes approximately 15,720 recursive calls to find all 92 solutions for n=8. With bit manipulation and symmetry breaking this can be reduced to around 2,000–3,000 effective operations. The exact count varies slightly by implementation.