Sudoku generator in Python with a unique-solution check
A sudoku with two answers is a misprint. About fifty lines of Python that fill a grid, empty it square by square and keep only puzzles with exactly one solution, and what 200 of them showed about "unique" against "easy".
A sudoku with two answers is broken. Somewhere in the middle the solver reaches a square where either of two digits works, and no amount of logic decides it. In a newspaper that is an apology on page two. In a printed puzzle book it is a one-star review.
So a generator needs two parts: one that makes puzzles, and one that proves each has exactly one solution. Here are both in plain Python, and what we saw when we ran them 200 times on 25 October 2026.
Count to two, not to one#
A solver that stops at the first answer tells you the puzzle can be solved. It does not tell you whether there is a second answer. The uniqueness check is the same solver told to keep going and stop at the second solution: zero means broken, one means good, two means broken in the other way.
It is fast enough in pure Python if the search always fills the square with the fewest possible digits first. A square with one option is filled at once, and a dead end shows up early as a square with none.
The generator#
Fill an empty grid with a random digit order, which gives a random complete grid. Then visit the 81 squares in random order. Empty each one, and keep it empty only if the puzzle still has one solution. When every square has been tried, no given can be taken away without opening a second answer.
import random
DIGITS = range(1, 10)
def peers(i):
r, c = divmod(i, 9)
b = 27 * (r // 3) + 3 * (c // 3)
return {9 * r + k for k in range(9)} | {9 * k + c for k in range(9)} | {b + 9 * y + x for y in range(3) for x in range(3)}
PEERS = [peers(i) - {i} for i in range(81)]
def solutions(grid, limit=2, rng=None):
"""Yield up to `limit` solutions of an 81-list (0 = empty). Fills the square with the fewest options first."""
found = 0
def search(g):
nonlocal found
best, options = None, None
for i in range(81):
if g[i] == 0:
opts = [d for d in DIGITS if all(g[p] != d for p in PEERS[i])]
if best is None or len(opts) < len(options):
best, options = i, opts
if len(opts) <= 1:
break
if best is None:
found += 1
yield list(g)
return
if rng:
rng.shuffle(options)
for d in options:
g[best] = d
yield from search(g)
if found >= limit:
break
g[best] = 0
yield from search(list(grid))
def is_unique(grid):
return len(list(solutions(grid, limit=2))) == 1
def make_puzzle(seed):
"""A full random grid, then empty squares in random order, keeping each removal only if one solution remains."""
rng = random.Random(seed)
grid = next(solutions([0] * 81, limit=1, rng=rng))
answer = list(grid)
for i in rng.sample(range(81), 81):
kept, grid[i] = grid[i], 0
if not is_unique(grid):
grid[i] = kept
return grid, answermake_puzzle(seed) returns the puzzle (0 for an empty square) and its answer. The same seed gives the same puzzle, which matters when you need to make one again.
What 200 of them gave#
We ran make_puzzle for seeds 0 to 199 and then checked every result with a second program that shares no code with the first: Knuth's Algorithm X, which treats sudoku as an exact cover problem over 324 constraints.
| Result | |
|---|---|
| Time | 199 s for 200 puzzles, about 1 s each |
| Givens | 21 to 28, median 24 |
| Algorithm X agrees: one solution | 200 of 200 |
| Answer is a valid grid holding every given | 200 of 200 |
| Solved by singles alone | 76 of 200 |
The second checker earns its place. One counter can carry one bug through every puzzle it ever checks. Two counters built differently have to share the same mistake to let a puzzle through.
Unique is not the same as easy#
Every one of those 200 puzzles has one answer, and every one is minimal: no given can go. That does not make them alike.
76 of them fall to singles alone: a square with only one possible digit (a naked single), or a digit with only one possible square in its row, column or box (a hidden single). The other 124 need at least one harder step, such as locked candidates or a pair. The number of givens does not tell them apart: both groups have a median of 24 givens. Grading puzzles as easy, medium and hard therefore takes a third program. It solves each puzzle the way a person would, one kind of step at a time, and records the hardest step it needed.
The two-answer trap#
The classic way to break a puzzle is a rectangle. Take two rows in the same band of boxes and two columns, where the four corners hold two digits crosswise, like 4 and 7 on one row and 7 and 4 on the other. Empty those four corners and nothing else, and both counters above report two solutions: the digits can swap. The generator never leaves that open, because the uniqueness check runs after every square it empties.
From a puzzle to a book#
One puzzle is a function call. A book is 150 of them, graded, laid out with margins that fit the page count, followed by the answers, with a cover whose spine matches the paper. Then the whole thing has to pass KDP's file checks.
That is what our puzzle book maker does. It checks every grid with Algorithm X before it reaches a page and grades levels by the steps a solver needs. You download the interior PDF, a full wrap cover and a signed report that checks both against KDP's paperback specs. It also makes word search, maze, killer sudoku, nonogram and a dozen more kinds. If you would rather do it yourself, the step-by-step guide to a KDP sudoku book covers trim sizes, page counts, line weights and the rules KDP applies to puzzle books.
written
- 3 min read
Cron does not mean what you think it means
Seven ways a crontab line does something other than what it reads like: step values that restart, two day fields that mean "or", a percent sign that eats your command.
- 2 min read
The key is already public: what to do after you commit a secret
Deleting the commit is not the fix. Revoke the key first, check who used it, then clean up and put a scanner in front of the next push.
- 3 min read
A timestamp without a zone is a guess
Scraped dates arrive as "2026-03-08 02:30" with nothing else. Some of those times never happened, some happened twice, and the gap between two cities moves during the year.