← Back to Blog

Dev Blog · May 21, 2026

Building Sudoku: A Classic Puzzle in 200 Lines

Sudoku

Sudoku seemed like the "safe" project on my list. It is a well-understood puzzle with clear rules, a finite solution space, and no moving parts. I figured I would knock it out in a weekend. Three weeks later I had learned more about backtracking solvers, uniqueness proofs, and input state management than I expected. "Simple" and "solved problem" turned out to mean very different things when you are the one implementing it.

Generating Valid Puzzles Without a Database

My first instinct was to ship with a hardcoded list of puzzles. That would have worked fine but felt wrong — I wanted the game to feel infinite. So I had to write a generator. A Sudoku generator is actually a solver running in reverse: you start with a solved grid and remove numbers. The solver part came together quickly using recursive backtracking with constraint propagation. The hard part was the removal step — you can not just randomly remove numbers and call the puzzle valid. Removing the wrong numbers creates puzzles with multiple solutions, which breaks the core Sudoku contract that there is exactly one correct answer. I implemented a uniqueness check that runs the solver after each removal and counts solutions, stopping at two — if it finds two, the removal is rejected and tried elsewhere. This keeps puzzles honest but is expensive at higher difficulty levels where many cells must be removed.

The Input State Problem on Mobile

Desktop Sudoku is straightforward: click a cell, press a number key, done. Mobile is a different problem. There is no keyboard. I needed a number picker UI, but where to put it — below the grid, overlaying the grid, or as a floating panel that follows the selected cell? I tried all three. Below the grid worked best for accessibility but made the interaction feel disconnected; you tap a cell, then look down to tap a number. The floating panel felt clever but got in its own way on smaller screens, covering adjacent cells the user might want to compare. I eventually landed on a fixed number bar below the grid, but with a highlight that shows which numbers are still available in the selected cell's row, column, and box. This made the number picker feel contextually smart rather than generic. Players could use it as a hint system without it ever being explicitly labelled as one.

Keeping 200 Lines Honest

The "200 lines" in the title is approximately true and intentional. I set myself the constraint early: if I could not explain the entire implementation in under 200 lines of annotated JavaScript, the design was too complex. This forced good decisions throughout. I used a flat 81-element array for the grid state rather than a nested array of arrays — simpler to index, simpler to copy for undo history. I wrote a single validate function that checks rows, columns, and boxes in one pass, called on every input event, rather than three separate validators. I skipped animation states entirely — cells just update. The constraint revealed what the game actually needed versus what I might have added if there had been no limit. Puzzle generation, validation, difficulty levels, mobile input, and a timer. Everything else was scope creep I was forced to cut. The game is better for it.

Browse All Games