Sudoku
backtracking algorithm
puzzle solving
algorithmic techniques
computational logic

Sudoku backtracking algorithm

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Sudoku solving is a classic constraint-satisfaction problem, and backtracking is the most practical exact algorithm for standard puzzles. The algorithm tries candidate values, recurses, and undoes decisions when constraints fail. Good implementation details, such as candidate ordering and fast validity checks, make a huge difference in runtime.

Core Sections

Model Sudoku constraints clearly

A valid Sudoku board must satisfy three constraints for every filled cell:

  • value appears once per row,
  • value appears once per column,
  • value appears once per 3x3 box.

Backtracking works by filling one empty cell at a time with candidates that satisfy all three constraints.

Baseline recursive solver

A simple implementation is easy to verify and is a good base for optimization.

python
1from typing import List, Tuple, Optional
2
3Board = List[List[int]]
4
5
6def find_empty(board: Board) -> Optional[Tuple[int, int]]:
7    for r in range(9):
8        for c in range(9):
9            if board[r][c] == 0:
10                return r, c
11    return None
12
13
14def is_valid(board: Board, row: int, col: int, num: int) -> bool:
15    if any(board[row][x] == num for x in range(9)):
16        return False
17    if any(board[x][col] == num for x in range(9)):
18        return False
19
20    br, bc = (row // 3) * 3, (col // 3) * 3
21    for r in range(br, br + 3):
22        for c in range(bc, bc + 3):
23            if board[r][c] == num:
24                return False
25    return True
26
27
28def solve(board: Board) -> bool:
29    empty = find_empty(board)
30    if empty is None:
31        return True
32
33    r, c = empty
34    for num in range(1, 10):
35        if is_valid(board, r, c, num):
36            board[r][c] = num
37            if solve(board):
38                return True
39            board[r][c] = 0
40
41    return False

This version is correct but can be slow for harder puzzles.

Improve performance with candidate sets

Repeated row and column scans are expensive. Keep row, column, and box sets for used values so validity checks are constant-time average.

python
def box_index(r: int, c: int) -> int:
    return (r // 3) * 3 + (c // 3)

With sets, adding and removing candidates during recursion is cheaper than scanning full board each time.

Use Minimum Remaining Values heuristic

Select the empty cell with fewest valid candidates first. This heuristic reduces branching early and often speeds up hard puzzles significantly.

Instead of first-empty selection, compute candidate counts for all empty cells and choose the smallest domain cell. If any cell has zero candidates, backtrack immediately.

Forward checking during assignment

After placing a number, update candidate options of related cells. If any related cell loses all candidates, undo immediately. This catches dead ends earlier than pure backtracking.

Forward checking plus MRV usually gives good practical performance without making solver too complex.

Validate board before solving

Do not run solver on invalid initial board. Pre-check duplicates in rows, columns, and boxes. Failing fast on invalid input avoids misleading recursion behavior and helps surface bad puzzle sources quickly.

Input validation should be explicit in production pipelines where puzzle data may come from external files.

Keep solver deterministic for testing

To make results reproducible, use fixed candidate order and deterministic tie-breakers in MRV selection. Deterministic behavior simplifies profiling, regression testing, and comparison across optimizations.

If you add randomization for puzzle generation workflows, keep it optional and seed-controlled.

Complexity discussion

Backtracking worst-case search is exponential, but Sudoku constraints prune aggressively in typical puzzles. Efficiency mostly depends on heuristics and validity-check implementation.

For standard 9x9 Sudoku, optimized backtracking is usually fast enough for interactive tools and APIs.

Practical testing strategy

Include test sets for:

  • easy puzzles,
  • hard puzzles,
  • already solved boards,
  • invalid boards,
  • unsolvable boards.

A solver should return success with valid solution or explicit failure without mutating board irreversibly.

Common Pitfalls

  • Forgetting to reset cell value during backtrack step.
  • Not validating initial board and chasing impossible states.
  • Using first-empty strategy only and suffering heavy branching on hard puzzles.
  • Mixing solver state in globals and introducing hidden mutation bugs.
  • Assuming every valid-looking puzzle has at least one solution.

Summary

  • Sudoku backtracking solves puzzles by incremental assignment and undo.
  • Correctness depends on row, column, and box constraint enforcement.
  • MRV and forward checking dramatically reduce search effort.
  • Fast validity data structures improve runtime on difficult boards.
  • Input validation and deterministic tests keep solver reliable in production use.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.