← All algorithms

N-Queens

Backtracking · view source on GitHub

Read this in: English · Português · Español

Category: Backtracking

The problem

Placing N queens on an N × N chessboard so that no two attack each other — no two sharing a row, a column, or a diagonal. Building every possible one-queen-per-row placement in full and checking each one's validity only at the end is O(n^n): the number of ways to assign one of n columns to each of n rows, checked only after every single assignment has already been fully built.

The solution

Check as you go instead of after the fact. Place queens one row at a time; before trying a column for the next row, confirm it doesn't conflict with any queen already placed. The moment a conflict is found, that entire remaining subtree — every placement that would have built on top of this doomed partial solution — is abandoned immediately, without ever being constructed. That's backtracking's defining move: prune as early as possible, not after the fact. The actual search tree explored ends up being a small fraction of the full n^n space, even though nothing here changes the problem's worst-case complexity class — it changes how much of that worst case is actually visited in practice.

flowchart TD
    A["row 0: try column 0"] --> B["row 1: column 0 conflicts (same column) → skip"]
    A --> C["row 1: column 2 is safe → place, continue to row 2"]
    C --> D["row 2: every column conflicts → dead end, backtrack to row 1"]
    C --> E["row 1: try column 3 instead"]

Classic example

classic/NQueens implements the row-by-row backtracking search plus bruteForceCountSolutions — build the whole board first, validate once at the end — included specifically for the benchmark below. NQueensTest checks the small board sizes with no solution (2×2 and 3×3 genuinely have none), verifies all 4-queens solutions are internally conflict-free by checking every pair of placed queens directly, confirms brute force agrees with backtracking on 5 queens, and — the classic "prove it's real" number — asserts 8 queens produces exactly 92 solutions, the count first published by Franz Nauck in 1850 and one of the most-cited results in recreational mathematics.

Applied example: BACEN settlement lane assignment

applied/SettlementLaneAssignment maps a settlement scheduling constraint onto the N-Queens shape directly: BACEN's end-of-day settlement window runs N batch reconciliation jobs across N parallel processing lanes, where no two jobs may share a lane, and no two jobs may be placed such that both their time-slot distance and their lane distance are equal — the diagonal-conflict pattern, standing in here for two jobs that would contend for the same downstream ledger-lock window. Job = row, assigned lane = column; everything N-Queens already solves applies unchanged. SettlementLaneAssignmentTest confirms 4 jobs have exactly 2 conflict-free assignments, and that 2 jobs have none.

Benchmark

./gradlew :backtracking:n-queens:jmh

Real run on this machine (JMH 1.37, JDK 26.0.2, 2 warmup + 3 measurement iterations, 1 fork). Board sizes are kept deliberately small — the same lesson this repo's longest-common-subsequence benchmark learned the hard way: 8^8 is already close to 16.8 million, and growing n much further would make brute force's runtime explode:

Cost n=6 n=7 n=8
backtracking 0.005 ms 0.035 ms 0.222 ms
brute force 0.512 ms 7.491 ms 208.593 ms

Brute force's growth tracks its combinatorial n^n shape closely: going from n=6 to n=7 predicts roughly 7^7 / 6^6 ≈ 17.65x and measured 14.63x; n=7 to n=8 predicts roughly 8^8 / 7^7 ≈ 20.37x and measured 27.85x. Backtracking's own growth doesn't reduce to one clean formula — how much of the tree gets pruned depends on the board size in a way that isn't a simple closed form — but it stayed dramatically smaller at every size: 101x faster at n=6, 214x faster at n=7, and ~940x faster at n=8, for the exact same 92-solution answer.

When not to use it

Test coverage

100% instruction coverage, 100% branch coverage (JaCoCo). Reproduce it yourself:

./gradlew :backtracking:n-queens:jacocoTestReport

Report at backtracking/n-queens/build/reports/jacoco/test/html/index.html.

Further reading

Unit tests

src/test/java/com/algorithms/backtracking/nqueens/classic/NQueensTest.java
package com.algorithms.backtracking.nqueens.classic;

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class NQueensTest {

    @Test
    void aSingleQueenOnAOneByOneBoardIsTheOnlySolution() {
        assertThat(NQueens.countSolutions(1)).isEqualTo(1);
    }

    @Test
    void twoAndThreeByTwoAndThreeBoardsHaveNoSolution() {
        assertThat(NQueens.countSolutions(2)).isZero();
        assertThat(NQueens.countSolutions(3)).isZero();
    }

    @Test
    void fourQueensHasExactlyTwoSolutionsAndEachIsConflictFree() {
        List<int[]> solutions = NQueens.solve(4);

        assertThat(solutions).hasSize(2);
        solutions.forEach(NQueensTest::assertNoTwoQueensAttackEachOther);
    }

    @Test
    void eightQueensHasTheWellKnownNinetyTwoSolutions() {
        // The famous result first published by Franz Nauck in 1850.
        assertThat(NQueens.countSolutions(8)).isEqualTo(92);
    }

    @Test
    void bruteForceAgreesWithBacktrackingOnFiveQueens() {
        assertThat(NQueens.bruteForceCountSolutions(5)).isEqualTo(NQueens.countSolutions(5));
        assertThat(NQueens.countSolutions(5)).isEqualTo(10);
    }

    @Test
    void rejectsABoardSizeBelowOne() {
        assertThatThrownBy(() -> NQueens.solve(0)).isInstanceOf(IllegalArgumentException.class);
        assertThatThrownBy(() -> NQueens.bruteForceCountSolutions(0)).isInstanceOf(IllegalArgumentException.class);
    }

    private static void assertNoTwoQueensAttackEachOther(int[] columns) {
        for (int row = 0; row < columns.length; row++) {
            for (int otherRow = row + 1; otherRow < columns.length; otherRow++) {
                assertThat(columns[row]).isNotEqualTo(columns[otherRow]);
                assertThat(Math.abs(columns[row] - columns[otherRow])).isNotEqualTo(Math.abs(row - otherRow));
            }
        }
    }
}
src/test/java/com/algorithms/backtracking/nqueens/applied/SettlementLaneAssignmentTest.java
package com.algorithms.backtracking.nqueens.applied;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class SettlementLaneAssignmentTest {

    private final SettlementLaneAssignment assignment = new SettlementLaneAssignment();

    @Test
    void findsAllConflictFreeLaneAssignmentsForFourJobs() {
        assertThat(assignment.nonConflictingAssignments(4)).hasSize(2);
    }

    @Test
    void reportsNoConflictFreeAssignmentExistsForTwoJobs() {
        assertThat(assignment.hasNonConflictingAssignment(2)).isFalse();
    }

    @Test
    void reportsAConflictFreeAssignmentExistsForFourJobs() {
        assertThat(assignment.hasNonConflictingAssignment(4)).isTrue();
    }
}

View full JaCoCo coverage report →