Home/Blog/Solving Sudoku with Integer Linear Programming
April 12, 2026
10 min read
7 views
πŸ‡ΊπŸ‡Έ English

Solving Sudoku with Integer Linear Programming

Sudoku is more than a newspaper puzzle. It can be modeled as an Integer Linear Program (ILP) with binary variables and linear constraints, then solved instantly by an optimization solver. We walk through the full formulation, a working Python implementation, and a step-by-step example.

algorithmsoptimizationsudokuinteger-linear-programmingILPpythonor-toolsconstraint-programming
Published in Algorithms
Sudoku grid solved with Integer Linear Programming

Introduction

If you've ever solved a Sudoku by hand you know the drill β€” scan rows, columns, boxes, pencil in candidates, eliminate, repeat. It gets the job done, but on hard puzzles it's painfully slow and you inevitably mess something up.

I wanted to see if there was a more… mechanical way to do it. Turns out, you can describe every single rule of Sudoku as a math equation, toss the whole thing at an optimization solver, and get the answer back in milliseconds. No guessing, no erasing.

The technique is called Integer Linear Programming (ILP). It's the same math behind airline scheduling, warehouse logistics, and chip design β€” except here we're using it to crack a newspaper puzzle. Feels a bit like bringing a cannon to a knife fight, but it works beautifully.


1. Sudoku Rules β€” Quick Refresher

A standard 9Γ—9 Sudoku grid has three rules:

  1. Row: each row contains digits 1–9 exactly once
  2. Column: each column contains digits 1–9 exactly once
  3. Box: each 3Γ—3 sub-grid contains digits 1–9 exactly once

Some cells come pre-filled (the "givens"). You fill the rest.

What's nice is that these three rules translate almost word-for-word into linear constraints.


2. The ILP Formulation

Decision Variables

We introduce binary variables:

xr,c,v∈{0,1}x_{r,c,v} \in \{0, 1\}

where:

  • rr = row (0–8)
  • cc = column (0–8)
  • vv = value (1–9)

xr,c,v=1x_{r,c,v} = 1 means "cell (r,c)(r, c) holds digit vv."

So that's 9Γ—9Γ—9=7299 \times 9 \times 9 = 729 binary variables total. Sounds like a lot, but it's tiny for a solver.

Constraints

1. One value per cell:

βˆ‘v=19xr,c,v=1βˆ€β€…β€Šr,c\sum_{v=1}^{9} x_{r,c,v} = 1 \quad \forall\; r, c

In plain English β€” for every cell, exactly one of the nine digit-slots is turned on.

2. Each digit once per row:

βˆ‘c=08xr,c,v=1βˆ€β€…β€Šr,v\sum_{c=0}^{8} x_{r,c,v} = 1 \quad \forall\; r, v

3. Each digit once per column:

βˆ‘r=08xr,c,v=1βˆ€β€…β€Šc,v\sum_{r=0}^{8} x_{r,c,v} = 1 \quad \forall\; c, v

4. Each digit once per 3Γ—3 box:

βˆ‘r=r0r0+2βˆ‘c=c0c0+2xr,c,v=1βˆ€β€…β€Šv,β€…β€Š(r0,c0)∈{0,3,6}2\sum_{r=r_0}^{r_0+2} \sum_{c=c_0}^{c_0+2} x_{r,c,v} = 1 \quad \forall\; v,\; (r_0, c_0) \in \{0, 3, 6\}^2

5. Pre-filled cells:

If the puzzle tells us cell (r,c)(r, c) is vβˆ—v^*, we pin it:

xr,c,vβˆ—=1x_{r,c,v^*} = 1

Objective Function

There isn't one β€” Sudoku is a feasibility problem, not an optimization problem. We just need any assignment that passes all the constraints. In practice solvers want something, so we set a dummy:

minβ‘β€…β€Š0\min \; 0

Summary

ComponentCount
Variables729 binary
Cell constraints81
Row constraints81
Column constraints81
Box constraints81
Total constraints324 + givens

For a modern solver, this is trivially small.


3. Python Implementation

Here's a complete solver using Google's OR-Tools (free, open-source). I've been using it for various projects and it handles this kind of thing effortlessly:

python
from ortools.sat.python import cp_model

def solve_sudoku(grid):
    """
    Solve a 9x9 Sudoku puzzle using Integer Linear Programming.
    grid: 9x9 list of ints (0 = empty cell)
    Returns the solved 9x9 grid, or None if unsolvable.
    """
    model = cp_model.CpModel()

    # Decision variables: x[r][c][v] = 1 if cell (r,c) holds digit v+1
    x = [[[model.NewBoolVar(f'x_{r}_{c}_{v}')
            for v in range(9)]
           for c in range(9)]
          for r in range(9)]

    # Constraint 1: each cell has exactly one digit
    for r in range(9):
        for c in range(9):
            model.Add(sum(x[r][c][v] for v in range(9)) == 1)

    # Constraint 2: each digit appears once per row
    for r in range(9):
        for v in range(9):
            model.Add(sum(x[r][c][v] for c in range(9)) == 1)

    # Constraint 3: each digit appears once per column
    for c in range(9):
        for v in range(9):
            model.Add(sum(x[r][c][v] for r in range(9)) == 1)

    # Constraint 4: each digit appears once per 3x3 box
    for box_r in range(3):
        for box_c in range(3):
            for v in range(9):
                model.Add(sum(
                    x[r][c][v]
                    for r in range(box_r * 3, box_r * 3 + 3)
                    for c in range(box_c * 3, box_c * 3 + 3)
                ) == 1)

    # Constraint 5: fix the givens
    for r in range(9):
        for c in range(9):
            if grid[r][c] != 0:
                model.Add(x[r][c][grid[r][c] - 1] == 1)

    # Solve
    solver = cp_model.CpSolver()
    status = solver.Solve(model)

    if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
        solution = []
        for r in range(9):
            row = []
            for c in range(9):
                for v in range(9):
                    if solver.Value(x[r][c][v]) == 1:
                        row.append(v + 1)
                        break
            solution.append(row)
        return solution

    return None  # no solution exists

Install OR-Tools:

bash
pip install ortools

4. Walking Through an Example

Let's throw a real puzzle at it. Here's one you might recognise from textbooks (0 means empty):

code
 5 3 .  | . 7 .  | . . .
 6 . .  | 1 9 5  | . . .
 . 9 8  | . . .  | . 6 .
---------+---------+--------
 8 . .  | . 6 .  | . . 3
 4 . .  | 8 . 3  | . . 1
 7 . .  | . 2 .  | . . 6
---------+---------+--------
 . 6 .  | . . .  | 2 8 .
 . . .  | 4 1 9  | . . 5
 . . .  | . 8 .  | . 7 9

Feed it in:

python
puzzle = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9],
]

solution = solve_sudoku(puzzle)

for row in solution:
    print(" ".join(str(d) for d in row))

Output:

code
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9

Done in under 10 ms. Every row, column, and 3Γ—3 box checks out.

What's happening inside the solver?

Roughly:

  1. It takes in the 729 variables and 324+ constraints
  2. Constraint propagation narrows things down β€” similar to what you'd do mentally, eliminating impossibilites
  3. When propagation stalls, the solver branches: picks an undecided variable, tries both values
  4. If it hits a contradiction, it backtracks
  5. Repeat until it finds a valid assignment (or proves there's none)

Modern solvers also throw in clause learning and cutting planes to speed things up further. For a problem this size it barely breaks a sweat.


5. How Does ILP Compare?

There are plenty of ways to solve Sudoku. Here's a rough comparison:

MethodIdeaSpeedFlexibility
BacktrackingTry a digit, undo on conflictFast on easy grids, can choke on hard onesLimited
Constraint PropagationRule out impossible candidatesVery fast but won't solve everything aloneModerate
Dancing Links (DLX)Exact cover via linked listsVery fastNarrow β€” built for exact cover
ILP / CP-SATMath model + solver engineVery fast, always terminatesVery flexible

Where ILP really shines is extensibility. Want to bolt on extra rules? Just add more constraints. No new algorithm needed:

  • Diagonal Sudoku β€” digits 1–9 on both main diagonals:
python
# main diagonal
for v in range(9):
    model.Add(sum(x[i][i][v] for i in range(9)) == 1)

# anti-diagonal
for v in range(9):
    model.Add(sum(x[i][8 - i][v] for i in range(9)) == 1)
  • Anti-King Sudoku β€” identical digits can't touch diagonally
  • Killer Sudoku β€” cages with sum constraints
  • Thermo Sudoku β€” digits increase along thermometer paths

Each variant is just a few extra lines. The solver doesn't care.


6. Beyond Sudoku

The modeling pattern we used here isn't specific to puzzles. The exact same approach works on real industrial problems:

ProblemVariablesConstraints
SudokuWhich digit goes whereRow/col/box uniqueness
Nurse schedulingWho works which shiftCoverage, rest rules, preferences
Vehicle routingWhich truck visits which stopCapacity, time windows, distance
Chip layoutWhere components goNo overlap, wire length, timing

The recipe is always:

  1. Define binary decision variables
  2. Express your rules as linear equalities / inequalities
  3. Optionally add an objective to minimize or maximize
  4. Let the solver handle the rest

Try It Yourself

Theory only gets you so far. Use the interactive solver below to enter any Sudoku and watch it get cracked in your browser β€” no server, no install. Pick one of the example puzzles or type your own.

<!-- interactive:sudoku-solver -->

Wrapping Up

Sudoku turns out to be a great on-ramp to Integer Linear Programming. The rules are intuitive, the formulation is clean (729 variables, 324 constraints), and the solver nails it in milliseconds. And the best part β€” the exact same framework scales to problems that matter in industry.

Next time you're staring at a hard grid, you can either keep erasing… or just model it and let the solver do the work.


Full code above is ready to copy-paste. Grab ortools and try it on your own puzzles.

Try It Yourself

Enter a Sudoku puzzle (or load an example), then click Solve. Use arrow keys to navigate. You can also paste a full 81-digit string.

Last updated: July 9, 2026

Get notified of new posts

No spam, unsubscribe anytime.