Algorithm

N-Queens

Backtracking Pattern

N-Queens

Place n queens on an n × n chessboard so that no two attack each other, and return every distinct arrangement.

A queen attacks along its entire row, its entire column, and both diagonals, without limit of distance. So a valid board has at most one queen in any row, any column, and any diagonal.

Each solution is returned as n strings of length n, using 'Q' for a queen and '.' for an empty square. Solutions may be returned in any order, and an empty list is correct when no arrangement exists.

CONSTRAINTS
  • 1 <= n <= 9
  • Exactly n queens must be placed
  • No two queens may share a row, a column, or either diagonal
  • Diagonals extend the full length of the board, not just adjacent squares
  • n = 2 and n = 3 have no solutions at all
EXAMPLE 1
Input: n = 4
Output: 2 solutions: [".Q..","...Q","Q...","..Q."] and ["..Q.","Q...","...Q",".Q.."]
In the first, queens sit at columns 1, 3, 0, 2 for rows 0 to 3. Check the closest pair: rows 0 and 1 hold columns 1 and 3 — different columns, and the row gap of 1 does not equal the column gap of 2, so they miss each other diagonally. The second solution is the mirror image of the first.
EXAMPLE 2
Input: n = 1
Output: 1 solution: ["Q"]
A single queen on a one-square board attacks nothing, since there is nothing else to attack. The smallest valid input, and it does have a solution.
EXAMPLE 3
Input: n = 2
Output: [] — no solutions
Two queens on a 2×2 board must occupy different rows and different columns, which forces them onto a diagonal, where they attack each other. Returning an empty list rather than an error is the required behaviour.
EXAMPLE 4
Input: n = 3
Output: [] — no solutions
Placing a queen in row 0 leaves at most one safe square in row 1, and that choice always leaves row 2 fully attacked. Both n = 2 and n = 3 are impossible, and every larger n has at least one solution.
What should be returned when no arrangement exists?
An empty list. This genuinely occurs at n = 2 and n = 3, so it is not a hypothetical edge case — it is in the test set.
Do reflections and rotations count as separate solutions?
Yes. The two solutions for n = 4 are mirror images of each other and both must be returned. Counting only distinct arrangements up to symmetry is a different and considerably harder problem.
In what format should each solution be returned?
As a list of n strings, each n characters long, with 'Q' marking a queen and '.' an empty square. Internally it is far easier to track just the column chosen for each row and build the strings at the end.
Does a queen attack along the whole diagonal or only adjacent squares?
The whole diagonal, to the edge of the board — same for rows and columns. Checking only neighbouring squares is a misreading that produces many invalid boards.

Every earlier problem in this section enumerated answers that were all valid by construction — every subset is a subset, every permutation is a permutation. This one is different: most arrangements are illegal, and the interesting work is refusing to build them.

The naive framing, and how bad it is

Stated plainly, the task is to choose n squares from n². For n = 8 that is C(64, 8) = 4,426,165,368 candidate boards, each then needing validation.

python
for squares in combinations(all_64_squares, 8):
    if no_two_attack(squares):
        record(squares)

Four billion candidates, and the overwhelming majority are absurd — most place several queens in the same row, which any human would reject at a glance.

Name the waste precisely: the search commits to all eight placements before checking any of them. A board with two queens in row 0 is doomed immediately, yet the enumeration continues to choose six more squares before discovering it. Every one of those later choices is wasted work on a branch that was already dead.

The fix has two halves, and both are worth extracting as general technique: make some illegal states unrepresentable, and detect the rest as early as possible.

Half one: build the constraint into the structure

No two queens may share a row, and there are n queens and n rows. So every row holds exactly one queen — not at most one, exactly one, since n queens must fit into n rows with no sharing.

That completely reframes the search. Do not choose squares. Choose, for each row, which column its queen occupies. A solution is now just a list of n column numbers.

The gain is dramatic. The search space drops from C(64,8) ≈ 4.4 billion to 8⁸ = 16.7 million, and — this is the real point — the row constraint can no longer be violated. It is not checked, because no representable arrangement breaks it.

Now notice a second constraint falls out the same way. No two queens may share a column either, so the list of column numbers has no repeats: it is a permutation of 0 to n-1. That cuts the space again, to 8! = 40,320.

Two of the three rules have been eliminated by choosing the right representation, before any checking code exists. Reducing four billion to forty thousand cost nothing but a change of viewpoint.

Half two: check diagonals, and check them early

Only the diagonal rule remains, and it must be tested. The key decision is when.

Placing all n queens and then testing is still the leaf-checking mistake. Instead test at every placement: before putting a queen in a row, confirm it conflicts with nothing already placed. A conflict at row 3 kills the entire subtree beneath it — all the arrangements of rows 4 through 7 that would have followed. Pruning near the root of the tree saves exponentially more than pruning near the leaves.

So the question becomes how to test a diagonal quickly.

Encoding the diagonals

Two squares share a descending diagonal — down-right — when moving down one row moves right one column. So along such a diagonal, row and column increase together, which means row - col stays constant. Every descending diagonal has its own value of row - col.

Two squares share an ascending diagonal — down-left — when moving down one row moves left one column. There, row increases as column decreases, so row + col stays constant.

Check it concretely: squares (0,0), (1,1) and (2,2) all give row - col = 0, and they do lie on one descending diagonal. Squares (0,2), (1,1) and (2,0) all give row + col = 2, and they lie on one ascending diagonal.

So each square belongs to exactly one diagonal of each family, identified by those two numbers. Keeping a set of occupied values for each family makes a safety test three hash lookups — constant time, with no scanning of the board at all.

python
def solve_n_queens(n):
    result = []
    placement = []                        # placement[r] = column of the queen in row r
    cols, desc, asc = set(), set(), set() # occupied columns, row-col values, row+col values

    def build(row):
        if row == n:
            result.append(["." * c + "Q" + "." * (n - c - 1) for c in placement])
            return

        for col in range(n):
            if col in cols or (row - col) in desc or (row + col) in asc:
                continue                  # PRUNE: attacked, so skip without recursing

            cols.add(col)                 # CHOOSE
            desc.add(row - col)
            asc.add(row + col)
            placement.append(col)

            build(row + 1)                # EXPLORE

            placement.pop()               # UN-CHOOSE — all four pieces of state
            cols.remove(col)
            desc.remove(row - col)
            asc.remove(row + col)

    build(0)
    return result
Crucial Noteall four pieces of state are undone, and missing any one silently breaks the search. Forgetting cols.remove(col) leaves that column permanently blocked, so later branches find fewer and fewer legal squares and the solution count comes out too low — with no error to point at the cause.
Crucial Notethe recursion advances to row + 1 unconditionally, never to row. Each row gets exactly one queen by construction, which is the structural guarantee doing its work. There is no need to decide how many queens a row receives.
Crucial Noterow - col can be negative, which is fine for a hash set but not for array indexing. If you prefer arrays over sets, offset by n-1 to shift the range into [0, 2n-2]. Indexing an array with a negative number wraps silently in Python and crashes in most other languages.
Worked example:n = 4
- Row 0. Nothing placed, so every column is free. Try column 0. Sets become cols {0}, desc {0}, asc {0}.
- Row 1. Column 0 is taken. Column 1 gives desc = 1-1 = 0, already occupied — that is the diagonal running down-right from (0,0), so it is rejected. Column 2 gives desc = -1 and asc = 3, both free. Place it.
- Row 2. Column 0 is taken. Column 1 gives asc = 3, occupied by the queen at (1,2) — rejected. Column 2 is taken. Column 3 gives desc = -1, occupied by (1,2) — rejected. Every column fails, so this branch is dead and the search backtracks without ever touching row 3.
- Back at row 1, undo column 2 and try column 3. desc = -2, asc = 4, both free. Place it.
- Row 2. Column 1 gives desc = 1, asc = 3 — both free, and column 1 is unoccupied. Place it.
- Row 3. Column 2 gives desc = 1... occupied by (2,1). Rejected. Column 3 is taken. Columns 0 and 1 are taken or conflict. Dead end, backtrack.
- The search continues, and starting from column 1 in row 0 it finds [1,3,0,2], then from column 2 it finds [2,0,3,1]. Columns 0 and 3 in row 0 yield nothing.

Two solutions, and they are mirror images. The important moment is row 2 in the first branch: the whole subtree covering row 3 was eliminated by a single failed row, which is exactly the saving that leaf-checking would have forfeited.

The transferable lesson

Two ideas carry well beyond chessboards.

Choose a representation that makes illegal states unrepresentable. Switching from "pick n squares" to "pick a column per row" removed the row constraint entirely and reduced the search space by five orders of magnitude, with no checking code written. Before optimising a search, ask whether the encoding can absorb some of the constraints.

Prune as early as the constraint allows. Testing validity at the leaf wastes the entire subtree; testing at each step kills bad branches near the root. The general form is: check partial solutions for violations as soon as enough information exists to detect them.

The diagonal encoding is worth remembering on its own. Anti-diagonals share row + col; main diagonals share row - col. That identity appears in matrix diagonal traversals, diagonal sorting problems, and anywhere a grid must be grouped by diagonal.

Recognition signals: place k items subject to conflict rules, arrangement with mutual exclusions, colour the map, assign without collisions. This family is called constraint satisfaction, and Sudoku is the next one.

Train the reflex: when most arrangements are invalid, do not generate and filter. Encode what you can into the structure, and test the rest at the earliest moment it can fail.

Interactive Strategy Visualization

N-Queens (Recursive DFS)

Board: 4x4
N-Queens Goal: Place 4 queens so no two attack each other.
MINDSET

We place queens row-by-row. At each cell, we verify vertical and diagonal constraints. If a row has no valid spot, the entire branch is invalid.

PRUNING

A brute-force search of all queen positions would be N^(2N). Backtracking row-by-row with safety checks reduces this to O(N!) and even less in practice.

O(C(n², n) × n²) Choose Squares Then Validate
O(N!) Row-by-Row With Linear Safety Scan
O(N!) With Constant-Time Conflict Sets