8 Queens Problem in Python - Complete Solution

A complete Python guide to solving the 8 queens problem using backtracking. Includes working code, line-by-line explanation, 92 solutions output, an optimized set-based version, and a generator variant using yield.

Problem Definition

The 8 queens problem asks: In how many ways can 8 queens be placed on an 8×8 chessboard so that no two queens attack each other? A queen can attack any piece in the same row, column, or diagonal.

The answer is 92 distinct solutions. If we exclude rotations and reflections that map solutions onto each other, there are 12 fundamentally distinct configurations.

This problem is a standard exercise in:

  • Constraint satisfaction
  • Recursive backtracking
  • Python closures and nested functions

It generalizes to the N queens problem, where the goal is to place N queens on an N×N board. Python's clean syntax makes it an ideal language for demonstrating the algorithm clearly.

Python Backtracking Solution

The following complete Python script finds all solutions to the N queens problem. Run it with python queens.py in any Python 3.x environment:

def solve_n_queens(n):
    solutions = []

    def is_safe(board, row, col):
        for i in range(row):
            if board[i] == col or abs(board[i] - col) == abs(i - row):
                return False
        return True

    def backtrack(board, row):
        if row == n:
            solutions.append(board[:])
            return
        for col in range(n):
            if is_safe(board, row, col):
                board[row] = col
                backtrack(board, row + 1)
                board[row] = -1

    backtrack([-1] * n, 0)
    return solutions


def print_board(solution):
    n = len(solution)
    for row in range(n):
        line = ""
        for col in range(n):
            line += "Q " if solution[row] == col else ". "
        print(line)
    print("---")


if __name__ == "__main__":
    n = 8
    solutions = solve_n_queens(n)
    print(f"Total solutions for {n}-Queens: {len(solutions)}")
    print_board(solutions[0])  # Print the first solution

Line-by-Line Explanation

Here is a detailed walkthrough of each part of the code above:

  • solve_n_queens(n) — The outer function. It initializes the solutions list and defines two nested helpers, then triggers the search by calling backtrack([-1]*n, 0).
  • is_safe(board, row, col) — Checks whether placing a queen at (row, col) is safe given the queens already placed in rows 0 through row-1. The condition board[i] == col detects column conflicts. The condition abs(board[i] - col) == abs(i - row) detects diagonal conflicts by comparing horizontal and vertical distances.
  • backtrack(board, row) — Recursive function. If row == n, all 8 queens are placed and we record a copy of board (using board[:] to avoid mutating the saved state). Otherwise, we try every column, recurse if safe, then reset board[row] = -1 to undo the placement.
  • board[:] copy — Critical! Without the slice copy, every recorded solution would reference the same mutable list, producing 92 identical entries.

The nesting of functions uses Python closures: is_safe and backtrack both close over solutions and n from the enclosing scope, avoiding the need to pass them as parameters repeatedly.

Expected Output (92 Solutions)

When you run the script with n=8, you should see:

Total solutions for 8-Queens: 92
Q . . . . . . .
. . . . Q . . .
. . . . . . . Q
. . . . . Q . .
. . Q . . . . .
. . . . . . Q .
. Q . . . . . .
. . . Q . . . .

The first solution corresponds to the queen placement [0, 4, 7, 5, 2, 6, 1, 3], meaning row 0 has its queen in column 0, row 1 in column 4, and so on.

You can verify the count: there are exactly 92 solutions for n=8. For n=1 there is 1 solution, for n=2 and n=3 there are 0 solutions, and for n=4 there are 2 solutions. See all 92 solutions displayed visually on this site.

Optimized Version Using Sets

The is_safe function in the basic version iterates through all placed queens on every check — O(row) per check. We can speed this up by maintaining three Python sets that track occupied columns and diagonals in O(1) lookup time:

def solve_n_queens_fast(n):
    solutions = []
    cols = set()
    diag1 = set()  # row - col is constant on main diagonal
    diag2 = set()  # row + col is constant on anti-diagonal

    def backtrack(board, row):
        if row == n:
            solutions.append(board[:])
            return
        for col in range(n):
            if col in cols or (row - col) in diag1 or (row + col) in diag2:
                continue
            # Place queen
            board[row] = col
            cols.add(col)
            diag1.add(row - col)
            diag2.add(row + col)
            # Recurse
            backtrack(board, row + 1)
            # Remove queen (backtrack)
            board[row] = -1
            cols.remove(col)
            diag1.remove(row - col)
            diag2.remove(row + col)

    backtrack([-1] * n, 0)
    return solutions

# Same 92 solutions, faster execution
print(len(solve_n_queens_fast(8)))  # 92

Generator Version with yield

Python generators allow you to yield solutions one at a time without storing them all in memory. This is ideal for very large n where storing all solutions would exhaust RAM:

def queens_generator(n):
    """Yields each solution as a tuple of column positions."""
    cols = set()
    diag1 = set()
    diag2 = set()
    board = [-1] * n

    def backtrack(row):
        if row == n:
            yield tuple(board)
            return
        for col in range(n):
            if col not in cols and (row - col) not in diag1 and (row + col) not in diag2:
                board[row] = col
                cols.add(col); diag1.add(row - col); diag2.add(row + col)
                yield from backtrack(row + 1)
                board[row] = -1
                cols.remove(col); diag1.remove(row - col); diag2.remove(row + col)

    yield from backtrack(0)


# Count without storing all solutions
count = sum(1 for _ in queens_generator(8))
print(f"Total: {count}")  # 92

# Get first solution
first = next(queens_generator(8))
print(first)  # (0, 4, 7, 5, 2, 6, 1, 3)

Testing Different Board Sizes

The same code works for any board size. Known solution counts:

nSolutionsApprox. time (Python)
11<1ms
42<1ms
64<1ms
892~10ms
10724~200ms
1214,200~5s
152,279,184~20 min

For n≥13, consider switching to the bit-manipulation approach (see the backtracking algorithm guide) or using PyPy instead of CPython for a 10–20× speed-up.

Frequently Asked Questions

How do I run the Python 8 queens solution?

Save the code to a file named queens.py and run python queens.py (or python3 queens.py on macOS/Linux) from a terminal. No external libraries are required — the solution uses only built-in Python features.

Why does the basic Python solution use board[:] instead of board?

Python lists are passed by reference. If you append board directly, every element in solutions will point to the same list object, and after the recursion completes they will all contain the same (final) state. Using board[:] creates a shallow copy of the current state at the moment of appending.

How can I make the Python N queens solver faster?

Use the set-based version to get O(1) conflict checks. For even better performance, switch to bit manipulation (described in the backtracking guide), use PyPy instead of CPython, or use numpy for large n. The generator version also avoids memory overhead when you only need to count solutions.

Does this Python code work for N queens (not just 8)?

Yes — the function is named solve_n_queens(n) for a reason. Pass any positive integer n and it returns all solutions for an n×n board. For n=1 it returns [[0]], for n=2 and n=3 it returns [], and for n=8 it returns all 92 solutions.