N-Queens
Place n queens on an n×n board so none attack each other, by filling one row at a time and pruning columns and diagonals already under attack.
Overview
N-Queens is the canonical constraint-satisfaction backtracking problem. Queens attack along rows, columns, and both diagonals. Because exactly one queen must sit in each row, the search places a queen in row 0, then row 1, and so on; the only decision per row is the column.
Three boolean arrays (or bitmasks) make the attack check O(1): cols[c], diag1[r − c + n − 1] (↘ diagonals have constant r − c), and diag2[r + c] (↙ diagonals have constant r + c). A row is never re-checked because the row index is the recursion depth.
Intuition
A mental model before the formal terms.
Walk down the board row by row, sliding a queen along each row until it lands on a square nobody attacks. If a row has no safe square, the previous queen must move — you go back up one row and slide it further right.
Every ↘ diagonal is a line where row − col stays the same; every ↙ diagonal keeps row + col constant. So "is this diagonal taken?" is one array lookup, not a scan of the board.
How it works
- State:
queens[row] = colfor placed rows, pluscols,diag1,diag2occupancy sets. - Base case:
row == n— every row has a queen; record the board. - For each
colin0..n-1: ifcols[col],diag1[row−col+n−1], ordiag2[row+col]is taken, skip (prune). - Otherwise mark all three, set
queens[row] = col, recurse withrow + 1, then unmark all three. - Bitmask version:
cols,d1,d2are integers; available squares are~(cols | d1 | d2) & fullMask; shiftingd1 << 1andd2 >> 1when moving to the next row keeps them aligned.
Why it works
Any valid solution has exactly one queen per row (n queens, n rows, no shared row), so restricting to one-queen-per-row loses nothing.
Attack constraints are monotone: a conflict between two placed queens cannot be undone by placing more queens, so pruning on the first conflict is safe.
The three arrays exactly encode the four attack lines (row is implicit), so the O(1) check is equivalent to scanning all previously placed queens.
Recognition
How to tell a problem wants this.
- Placing items on a grid subject to pairwise "cannot share a line" constraints.
n ≤ 9..14and the output is all valid boards or their count.- Any "assign one value per row/slot with conflict rules" problem — the same skeleton solves graph coloring and Sudoku Solver.
Interactive visualization
Play, step, change the input. ← → and space work too.
1solve(row):2 if row == n: record(board); return3 for col in 0 .. n-1:4 if attacked(row, col): continue5 place(row, col)6 solve(row + 1)7 remove(row, col) // backtrackPseudocode
1solve(row):2 if row == n: record(queens); return3 for col in 0..n-1:4 if cols[col] or d1[row-col+n-1] or d2[row+col]: continue5 place(row, col) # mark cols, d1, d2; queens[row] = col6 solve(row + 1)7 remove(row, col) # unmark8solve(0)Implementations
1def solve_n_queens(n: int) -> list[list[str]]:21 · Attack-line bookkeeping arrays3 cols = [False] * n4 d1 = [False] * (2 * n - 1) # row - col + n - 15 d2 = [False] * (2 * n - 1) # row + col6 queens = [-1] * n7 out: list[list[str]] = []8 9 def solve(row: int) -> None:102 · Base case: build the board11 if row == n:12 out.append(["." * c + "Q" + "." * (n - c - 1) for c in queens])13 return14 for col in range(n):15 a, b = row - col + n - 1, row + col163 · Prune attacked squares17 if cols[col] or d1[a] or d2[b]:18 continue194 · Place / recurse / remove20 cols[col] = d1[a] = d2[b] = True21 queens[row] = col22 solve(row + 1)23 cols[col] = d1[a] = d2[b] = False24 25 solve(0)26 return out27 28 295 · Bitmask counter (solutions only)30def count_n_queens(n: int) -> int:31 full = (1 << n) - 132 33 def go(cols: int, d1: int, d2: int) -> int:34 if cols == full:35 return 136 total = 037 avail = full & ~(cols | d1 | d2)38 while avail:39 bit = avail & -avail # lowest set bit40 avail ^= bit41 total += go(cols | bit, (d1 | bit) << 1 & full, (d2 | bit) >> 1)42 return total43 44 return go(0, 0, 0)[False] * (2 * n - 1)allocates each diagonal marker list;queensstores the column per row.- The board is rendered with a list comprehension only when
row == n. - The prune
if cols[col] or d1[a] or d2[b]: continueis O(1) per square. - Chained assignment
cols[col] = d1[a] = d2[b] = Trueassigns the same value to all three targets. - The bitmask counter relies on Python ints being unbounded, so
& fullis needed to drop bits shifted past n.
The bitmask version is several times faster in CPython because it avoids list indexing.
- Python has no fixed-width ints, so
avail & -availworks for any n, but masks grow unless masked withfull. - Sets (
set()of used columns/diagonals) are a readable alternative to boolean lists with similar speed. - Depth n recursion is far below the default limit.
- Appending
queens(the shared list) to results instead of rendering it or copying it. - Forgetting the
+ n - 1offset for the anti-diagonal. - Omitting
& fullin the bitmask variant so the left-shifted diagonal keeps growing.
- Bitmask limits: C++
intand JS/TS bitwise ops are 32-bit (n <= 30 practical); Python ints are unbounded, so& fullis what keeps masks small, not a hard limit. - C++ groups state in a class; the others use closures over local arrays.
- C++
vector<bool>is bit-packed with proxy references; the chained assignment still works butbool&bindings do not.
Complexity
Upper bound: row r has at most n − r safe columns. In practice pruning makes it far faster; n = 8 has 92 solutions and visits about 2,000 nodes. Building each output board costs O(n²).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Constraint satisfaction with one decision per row/slot and cheap conflict checks.
- Counting or listing all solutions for
n ≤ 14; the bitmask variant handlesn = 15..16in seconds.
- You only need one solution for large
n— an explicit construction placesnqueens inO(n)for everyn ≥ 4. - Constraints are not monotone (a conflict can be fixed by later moves) — then pruning is unsound and you need a different search.
Alternatives
Common mistakes
- Using
row − coldirectly as an index without the+ n − 1offset (negative index). - Checking the column but forgetting one of the two diagonal families.
- Mutating a shared board array and pushing it into results without copying.
- Restoring only some of the marks on the way back (e.g.
colsbut notd1).
Interview patterns
- N-Queens I (list boards) and II (count only, ideal for the bitmask version).
- Same skeleton for graph coloring, Latin squares, and knight/bishop placement variants.
- Ask which constraints are monotone before choosing the prune — the interviewer wants to hear the soundness argument.
- Recursion versus iterationIntermediate
- Recognizing a dynamic-programming problemAdvanced
- Word SearchAdvanced