Recursion & Backtracking · Recursion & Backtracking Fundamentals

Constraint Satisfaction Backtracking

Filling in unknowns one at a time, trying every locally-valid value at each position and backtracking the instant a placement violates a constraint - so invalid branches are abandoned as early as possible instead of being discovered only once fully built.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: choose the most constrained unknown first
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Sudoku, N-Queens, map coloring and timetabling share one shape. There's a set of unknowns, such as cells, rows, regions or time slots. Each needs a value, and rules forbid certain combinations. Trying every complete assignment is hopeless: 8 queens, one per row, already have 16,777,216. Constraint-satisfaction backtracking fills unknowns one at a time and rejects a value the moment it breaks a rule. Good bookkeeping and a good choice of which unknown to fill next make it fast on realistic inputs.

We'll go from simple to more technical:

  1. The pattern: place, check the rules immediately, recurse, undo, using O(1) lookups.
  2. A second flavor: fill the most constrained unknown first.
  3. Where constraint solving runs in production (scheduling, configuration, puzzles).
  4. What breaks it: late checks, bookkeeping that isn't undone, and slow checks.

The approach

Java

boolean solve(State state, int position) {
    if (position == totalPositions) return true;          // every unknown filled legally
    for (int value : candidatesFor(position)) {
        if (!isAllowed(state, position, value)) continue;  // check BEFORE going deeper
        place(state, position, value);
        if (solve(state, position + 1)) return true;       // stop at the first full solution
        unplace(state, position, value);                   // undo - try the next value
    }
    return false;                                          // nothing fits here: the caller backtracks
}

Python

def solve(state: State, position: int) -> bool:
    if position == total_positions:
        return True                                    # every unknown filled legally
    for value in candidates_for(position):
        if not is_allowed(state, position, value):
            continue                                   # check BEFORE going deeper
        place(state, position, value)
        if solve(state, position + 1):
            return True                                # stop at the first full solution
        unplace(state, position, value)                # undo - try the next value
    return False                                       # nothing fits here: the caller backtracks

The check comes before the recursive call, so a partial assignment that already breaks a rule is never extended. For 8 queens, placing row by row and skipping attacked columns and diagonals places only 2,056 queens in total to find all 92 solutions. Returning true up the stack stops at the first solution (Sudoku); counting instead of returning explores everything (N-Queens counts).

The checks have to be cheap. Keep lookup structures in step with the board - which digits each row, column and 3 x 3 box already has; which columns and diagonals already hold a queen - so isAllowed is O(1). Cells on the same "" diagonal share r - c, cells on the same "/" diagonal share r + c, so two boolean arrays of size 2n - 1 cover all diagonals.

A second flavor: choose the most constrained unknown first

The order in which unknowns are filled doesn't change what's found, but it hugely changes how much is explored. Filling the empty cell with the fewest remaining candidates first (the "most constrained variable" heuristic) fails fast on dead ends and makes forced moves immediately:

Java

int bestR = -1, bestC = -1, fewest = 10;
for (int r = 0; r < 9; r++)
    for (int c = 0; c < 9; c++)
        if (board[r][c] == 0) {
            int options = countAllowedDigits(r, c);
            if (options < fewest) { fewest = options; bestR = r; bestC = c; }
        }
if (bestR == -1) return true;                              // no empty cell left: solved
// try each allowed digit at (bestR, bestC), recurse, undo

Python

def solve(board: list[list[int]]) -> bool:
    best, fewest = None, 10
    for r in range(9):
        for c in range(9):
            if board[r][c] == 0:
                options = count_allowed_digits(r, c)
                if options < fewest:
                    best, fewest = (r, c), options
    if best is None:
        return True                                    # no empty cell left: solved
    # try each allowed digit at best, recurse, undo

On the classic sample puzzle, row-by-row filling makes 4,208 placements; most-constrained-first makes 51. A cell with zero options ends the branch at once, instead of after filling everything before it.

Where you'll see it

Scheduling (exam timetables, nurse rosters, sports fixtures), resource placement (VMs onto hosts respecting capacity and anti-affinity) and configuration (package version resolution) are constraint satisfaction. Production solvers - SAT and CP-SAT solvers, dependency resolvers - are this backtracking loop plus propagation (removing values ruled out by each placement), smart variable ordering and learning from conflicts.

Which tool for which job?

You needUse
Any CSP (Sudoku, N-Queens, coloring)place, check at once, recurse, undo
Fast rule checksused-value sets or bitmasks per row, column and box
Fewer dead endsthe most constrained unknown first
Even fewer guessespropagation: fill single-candidate unknowns, fail on zero
Symmetric puzzlessymmetry breaking (e.g. N-Queens' first row in the left half)
Large real-world problemsa SAT or constraint-programming solver

Common mistakes

MistakeWhat happensDo this instead
Checking only complete assignments16.8 million for 8 queensreject a value as soon as it breaks a rule
Undoing the board but not the bookkeepingvalid placements are rejected; "no solution"undo every structure you updated
Scanning the board for each checkevery candidate costs O(n)O(1) sets or bitmasks
Filling unknowns in a fixed order4,208 placements instead of 51 on a sample Sudokumost constrained first

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why check a constraint before recursing rather than when the assignment is complete?

Show answer

A partial assignment that already breaks a rule can never become valid, so every extension of it is wasted work. Checking at placement abandons the whole subtree at once: 8 queens needs 2,056 placements that way, versus 16,777,216 complete one-per-row assignments to check at the end.

2. A Sudoku solver clears the cell when it backtracks but leaves the digit marked in its row/column/box sets. What happens?

Show answer

The digit stays "used" in those sets, so later branches can't place it where it's actually legal. The solver rejects valid placements and can fail to find any solution - on the sample puzzle it reports none. Every undo must restore all the bookkeeping the placement changed.

3. How do two arrays of size 2n - 1 track every diagonal for N-Queens?

Show answer

On a "" diagonal, r - c is constant (ranging from -(n - 1) to n - 1, shifted by n - 1 to index an array); on a "/" diagonal, r + c is constant (0 to 2n - 2). A queen at (r, c) marks both entries, so "is this diagonal attacked?" is one array lookup.

Key takeaways

  • Fill unknowns one at a time; check each value against the rules before recursing, and undo on the way back.
  • Keep O(1) lookup structures (per row/column/box, per column/diagonal) in step with every placement and undo.
  • Return true to stop at the first solution; count to enumerate all.
  • Filling the most constrained unknown first often cuts the search dramatically (4,208 to 51 placements on one Sudoku).
  • The worst case stays exponential - CSPs are NP-complete in general.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Any CSPplace, check, recurse, undo
O(1) checksbitmasks
Fewer dead endsmost constrained first
Fewer guessespropagation
Symmetric puzzlessymmetry breaking
Real schedulingCP-SAT / SAT solvers

Snippets worth knowing by heart

Java

int solve(int row, int cols, int d1, int d2) {       // N-Queens count
    if (row == n) return 1;
    int count = 0, free = ~(cols | d1 | d2) & full;
    while (free != 0) {
        int bit = free & -free; free -= bit;
        count += solve(row + 1, cols | bit, ((d1 | bit) << 1) & full, (d2 | bit) >> 1);
    }
    return count;
}

Python

def candidates(r, c):                    # Sudoku digits allowed at (r, c), as bits 1-9
    return ~(rows[r] | cols[c] | boxes[r // 3 * 3 + c // 3]) & 0x3FE

The traps

  • Checking only complete assignments: 8 queens tries 16.8 million boards. Reject a value as soon as it breaks a rule.
  • Bookkeeping that isn't undone: valid placements get rejected and you report "no solution". Undo every structure you updated.
  • Board scans per check: every candidate costs O(n). Track rows, columns and boxes in sets or bitmasks.
  • A fixed fill order: the search explores far more placements than needed. Fill the most constrained cell first.

Staff-level points, one line each

  • 12-queens with bitmasks: 14,200 solutions in 856,189 nodes.
  • Mirror symmetry: 2 × 7,100 from 428,100 nodes, half the search.
  • A hard Sudoku: 10,101 guesses with most-constrained-first, 1,850 with single-candidate propagation.
  • Propagation plus a good fill order is the core of every constraint solver.
  • Budget the search in production; the worst case is exponential.

Say it in an interview

  • "I reject a value as soon as it breaks a rule, with O(1) bitmask checks."
  • "I fill the cell with the fewest candidates first, so dead ends fail fast."
  • "Propagating forced moves cuts the guessing a lot."
  • "For real scheduling I'd model it for a CP-SAT solver."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Constraint Satisfaction Backtracking free

M-Coloring Problem

MediumProCoding problemJava · Python

Given an undirected graph with n vertices (0-indexed) and a list of edges, and an integer m, return whether the graph's vertices can be colored using at most m colors so that no edge connects two vertices of the same color.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Sudoku Solver

HardProCoding problemJava · Python

Solve a 9x9 Sudoku puzzle in place. Empty cells are given as 0. Each puzzle has exactly one valid solution.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Constraint Satisfaction Backtracking free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime