I have always been the kind of person who does crossword puzzles and sudoku on paper. There is something about the tactile act of writing numbers on a grid - erasing mistakes, working through logic chains with a pencil - that I find genuinely relaxing in a way that staring at a phone screen is not.
So when I found myself wanting a fresh sudoku puzzle one evening and could not find a printable version I was happy with, my first thought was not to look harder. My first thought was: I could build this.
That decision sent me down a surprisingly deep rabbit hole.
I assumed generating sudoku puzzles would be straightforward. After all, the rules are simple: a 9x9 grid where every row, column, and 3x3 box contains the digits 1 through 9 exactly once. How hard could it be to write code that produces these?
Pretty hard, it turns out.
Generating a valid completed grid is not too bad - you can start with a known solution and apply a series of valid transformations (rotating, reflecting, swapping rows within bands) to produce new grids. But generating a puzzle from a completed grid - deciding which numbers to remove so the puzzle has exactly one solution - is an NP-hard problem in general.
The key constraint is unique solvability. A valid sudoku puzzle must have exactly one solution. If you remove too many numbers, you may end up with a puzzle that has multiple solutions - which is considered invalid. If you do not remove enough, the puzzle is trivially easy.
I spent about a week working through different approaches before landing on a backtracking algorithm that starts with a complete grid, removes a number from a random cell, checks if the resulting puzzle still has exactly one solution, continues removing numbers if yes, and stops when the desired number of removed cells is reached. The solution-counting step is the expensive one. I implemented a constraint propagation approach that prunes the search space significantly before resorting to full backtracking.
Once I had working puzzle generation, I needed to implement difficulty levels - easy, medium, hard, expert. My naive assumption was that difficulty would scale directly with the number of empty cells. More empty cells equals harder puzzle. Simple.
This is partially true but mostly wrong.
Difficulty in sudoku is really about the solving techniques required. An easy puzzle can be solved entirely with the most basic technique: if a cell has only one possible value given what is already in its row, column, and box, fill it in. A harder puzzle requires more sophisticated techniques: naked pairs, hidden sets, X-wings, swordfish, and at the extreme end, chains and forcing nets.
A puzzle with 50 empty cells might be trivially solvable using only basic techniques. A puzzle with 35 empty cells might require advanced techniques that a beginner would find impossible.
I ended up implementing a puzzle difficulty analyzer that played through the puzzle using different technique levels and graded difficulty based on which techniques were required rather than how many cells were empty. This added significant complexity to the codebase but produced much more accurately graded puzzles.
Having working puzzle generation was only half the problem. The other half was making the printed output actually look good.
I spent more time than I expected on the visual design of the puzzle grid. The border widths needed to clearly distinguish the 3x3 boxes from the individual cells - the outer box borders needed to be noticeably heavier than the inner cell borders. The number font needed to be clear and legible at small sizes. The cell sizes needed to be comfortable to write in without taking up too much page space.
I also added optional features: a title area where you could add a custom heading, the difficulty level displayed above the grid, the solution printed separately at the bottom of the page for self-checking, and a batch mode that would generate multiple puzzles of different difficulties on a single page - useful for teachers creating a homework sheet.
After launching the Sudoku Printable generator, I got feedback that shaped several useful improvements.
A teacher who used it for her third-grade class pointed out that the number font was too small for young children to read comfortably. I added a font size option and a lower-density layout mode specifically for younger kids.
A user who was visually impaired asked if I could increase the contrast and line weight options. I added high-contrast mode and adjustable grid line widths.
Several users asked about 6x6 puzzles for beginners and 16x16 puzzles for experts. The 6x6 was relatively easy to add. The 16x16 required significant algorithmic work - the solving and generation logic needed to generalize to different grid sizes. But it made the tool substantially more useful for a wider audience.
Building this taught me a surprising amount about puzzle design as a discipline. Good puzzles are not just valid - they are elegant. The best sudoku puzzles have a kind of narrative flow where each step of the solution feels satisfying and fair. They do not require guessing. They reward logical thinking.
That is a hard thing to systematically generate, and honestly, I have not fully solved it. My generator produces valid, uniquely solvable puzzles with accurately rated difficulty. But whether they feel good to solve - whether they have that quality that makes someone say this is a well-constructed puzzle - is something I am still working on.
Maybe that is the next version. For now, the tool does what I built it to do: gives me fresh puzzles to print and solve with a pencil on a Sunday morning.