What Goes Into Generating a Sudoku Puzzle?
Have you ever wondered how sudoku puzzles are made? Behind every clean 9×9 grid with its carefully placed clue numbers lies a surprisingly elegant sudoku algorithm — a combination of randomized search, constraint checking, and mathematical verification that ensures every puzzle has exactly one valid solution.
At Sudoku King, our sudoku generator creates thousands of unique puzzles, each crafted by code rather than by hand. In this article, we’ll walk you through the entire puzzle generation pipeline: from filling an empty grid using a backtracking algorithm, to strategically removing cells while preserving solution uniqueness, to grading puzzles by difficulty.
Whether you’re a curious puzzler, a computer science student, or a developer looking to build your own sudoku generator, this guide will give you a complete understanding of how the process works — with real TypeScript code from our production engine.
💡 Quick Overview
Sudoku generation happens in two phases: (1) Generate a complete, valid solution grid using backtracking with randomization, then (2) Remove cells one at a time, verifying after each removal that the puzzle still has exactly one solution.
Step 1: Backtracking & Grid Generation
The first step in any sudoku generator is creating a fully solved 9×9 grid. This is a classic constraint satisfaction problem — we need to place digits 1–9 in every cell such that no digit repeats in any row, column, or 3×3 box.
The workhorse algorithm for this is backtracking — a form of depth-first search. Here’s how the backtracking algorithm for sudoku works:
- Start at cell (0, 0) — the top-left corner of the grid.
- Try placing a random digit from 1–9 (we shuffle the order to produce different puzzles each time).
- Check if the digit is valid — meaning it doesn’t already appear in the same row, column, or 3×3 box.
- If valid, move to the next cell and repeat.
- If no valid digit exists for the current cell, backtrack — undo the last placement and try a different digit.
- Continue until all 81 cells are filled.
The key to making this a generator rather than a solver is the randomization. By shuffling the candidate digits before trying them, we ensure that every run produces a different valid grid. Here’s the actual TypeScript code from our engine:
function generateSolvedGrid(rng?: () => number): SudokuGrid {
const grid: SudokuGrid = Array.from(
{ length: 9 },
() => Array(9).fill(0)
);
function fill(pos: number): boolean {
if (pos === 81) return true; // All cells filled!
const row = Math.floor(pos / 9);
const col = pos % 9;
// Shuffle digits 1-9 for randomized generation
const nums = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9], rng);
for (const num of nums) {
if (isValid(grid, row, col, num)) {
grid[row][col] = num;
if (fill(pos + 1)) return true;
grid[row][col] = 0; // Backtrack
}
}
return false;
}
fill(0);
return grid;
}The fill() function is recursive. It walks through positions 0–80 (the 81 cells), and at each position tries shuffled digits. If it reaches position 81, the grid is complete. If it gets stuck, the recursion naturally backtracks to try alternative digits at earlier positions.
⏱️ Performance Note
Despite trying up to 9 possibilities at each of 81 cells, the backtracking algorithm is remarkably fast for sudoku. Constraint checking prunes the search tree aggressively — in practice, a solved grid is generated in under 10 milliseconds.
Step 2: Constraint Propagation — The Validation Engine
At the heart of both generation and solving lies constraint propagation — the process of checking whether a digit placement violates any of sudoku’s three rules. Every time the algorithm tries to place a number, it must verify three constraints simultaneously:
Row Constraint
The digit must not already appear in the same row (all 9 cells in the horizontal line).
Column Constraint
The digit must not already appear in the same column (all 9 cells in the vertical line).
Box Constraint
The digit must not already appear in the same 3×3 box (the nine cells sharing the same thick-bordered region).
Here’s the isValid function — the constraint propagation engine that powers every placement decision:
function isValid(
grid: SudokuGrid,
row: number,
col: number,
num: number
): boolean {
// Check row — no duplicate in this horizontal line
for (let c = 0; c < 9; c++) {
if (grid[row][c] === num) return false;
}
// Check column — no duplicate in this vertical line
for (let r = 0; r < 9; r++) {
if (grid[r][col] === num) return false;
}
// Check 3×3 box — no duplicate in this region
const boxRow = Math.floor(row / 3) * 3;
const boxCol = Math.floor(col / 3) * 3;
for (let r = boxRow; r < boxRow + 3; r++) {
for (let c = boxCol; c < boxCol + 3; c++) {
if (grid[r][c] === num) return false;
}
}
return true; // All constraints satisfied
}This function runs thousands of times during puzzle generation. Its efficiency is critical — by checking constraints before placing a digit, we prune enormous branches of the search tree. Without constraint propagation, the backtracking algorithm would need to explore an astronomically larger space (there are approximately 6.67 × 1021 possible ways to fill a 9×9 grid, but only about 5.47 × 109 valid sudoku solutions).
The box constraint calculation is worth noting: Math.floor(row / 3) * 3 maps any row index to the top row of its 3×3 box. For example, rows 0, 1, 2 all map to box-row 0; rows 3, 4, 5 map to box-row 3; and rows 6, 7, 8 map to box-row 6. This elegant integer arithmetic avoids the need for lookup tables.
Step 3: Solution Uniqueness Verification
A proper sudoku puzzle must have exactly one solution. This is what separates a legitimate puzzle from a random grid with holes in it. The uniqueness guarantee is what makes sudoku a game of pure logic — every cell can be determined through deduction alone, without guessing.
After generating a complete solution grid, our sudoku generator creates the puzzle by strategically removing cells. But we can’t just remove cells randomly — each removal must be verified to ensure the puzzle still has a unique solution.
The verification process uses a modified version of the same backtracking solver, but instead of finding a solution, it counts solutions — stopping as soon as it finds two:
function countSolutions(
grid: SudokuGrid,
limit: number = 2
): number {
const g = copyGrid(grid);
let count = 0;
function solve(pos: number): boolean {
if (count >= limit) return true; // Early exit!
// Find next empty cell
while (pos < 81) {
const row = Math.floor(pos / 9);
const col = pos % 9;
if (g[row][col] === 0) break;
pos++;
}
if (pos === 81) {
count++; // Found a complete solution
return count >= limit;
}
const row = Math.floor(pos / 9);
const col = pos % 9;
for (let num = 1; num <= 9; num++) {
if (isValid(g, row, col, num)) {
g[row][col] = num;
if (solve(pos + 1)) {
g[row][col] = 0;
return true;
}
g[row][col] = 0;
}
}
return false;
}
solve(0);
return count;
}The critical optimization here is the limit parameter. We don’t need to count all possible solutions — we only need to know if there’s more than one. Setting limit = 2 means the solver stops the moment it finds a second solution, saving enormous computation time.
The cell removal process works like this:
- Create a shuffled list of all 81 cell positions.
- For each position, temporarily remove the digit (set it to 0).
- Run
countSolutions()on the modified grid. - If the count is exactly 1 — the puzzle still has a unique solution — keep the cell empty.
- If the count is 2 or more — removing this cell creates ambiguity — restore the digit.
- Continue until the target number of cells have been removed.
// Cell removal loop from generateSudoku()
const shuffledPositions = shuffle(positions, rng);
let removed = 0;
for (const [row, col] of shuffledPositions) {
if (removed >= cellsToRemove) break;
const backup = puzzle[row][col];
puzzle[row][col] = 0;
// Verify uniqueness — must have exactly 1 solution
if (countSolutions(puzzle) !== 1) {
puzzle[row][col] = backup; // Restore — can't remove
} else {
removed++; // Safe to remove
}
}💡 Why Shuffling Matters
The order in which cells are removed affects the final puzzle. Shuffling the position list ensures that each generated puzzle has a different pattern of given clues, even when starting from similar solution grids. This is why every Sudoku King puzzle feels unique.
Step 4: Difficulty Grading
How do you make a sudoku puzzle “easy” or “hard”? The primary factor is the number of given clue cells — fewer clues means more deduction required, which typically means harder puzzles.
Our sudoku generator uses three difficulty tiers:
Easy
~40 cells removed
~41 given clues
Medium
~50 cells removed
~31 given clues
Hard
~56 cells removed
~25 given clues
function getCellsToRemove(difficulty: Difficulty): number {
switch (difficulty) {
case "easy":
return 40; // ~41 given cells
case "medium":
return 50; // ~31 given cells
case "hard":
return 56; // ~25 given cells
default:
return 40;
}
}It’s important to note that clue count is a heuristic for difficulty, not a perfect measure. A puzzle with 30 clues could theoretically be easier than one with 35 clues, depending on which cells are given. The arrangement of clues determines which solving techniques are needed.
Easy puzzles can typically be solved using only naked singles and hidden singles — the simplest techniques. Medium puzzles often require naked pairs and pointing pairs. Hard puzzles may demand advanced techniques like X-Wing and Swordfish. If you want to learn these techniques, check out our complete solving guide.
🧠 Advanced Difficulty Grading
More sophisticated generators analyze which solving techniques are required and rate difficulty based on technique complexity rather than just clue count. This is an area of active research — some generators even use machine learning to predict human solve times. Our approach balances simplicity with effective results.
Putting It All Together
Here’s the complete sudoku algorithm that ties everything together — the main generateSudoku function:
export function generateSudoku(
difficulty: Difficulty = "easy",
seed?: number
): {
puzzle: SudokuGrid;
solution: SudokuGrid;
difficulty: Difficulty;
} {
const rng = seed !== undefined
? createSeededRandom(seed)
: undefined;
// Step 1: Generate complete solution grid
const solution = generateSolvedGrid(rng);
const puzzle = copyGrid(solution);
const cellsToRemove = getCellsToRemove(difficulty);
// Step 2: Shuffle cell positions
const positions: [number, number][] = [];
for (let r = 0; r < 9; r++)
for (let c = 0; c < 9; c++)
positions.push([r, c]);
const shuffledPositions = shuffle(positions, rng);
// Step 3: Remove cells with uniqueness check
let removed = 0;
for (const [row, col] of shuffledPositions) {
if (removed >= cellsToRemove) break;
const backup = puzzle[row][col];
puzzle[row][col] = 0;
if (countSolutions(puzzle) !== 1) {
puzzle[row][col] = backup;
} else {
removed++;
}
}
return { puzzle, solution, difficulty };
}Notice the optional seed parameter. When provided, it creates a deterministic random number generator (using the Mulberry32 algorithm) so that the same seed always produces the same puzzle. We use this for our Daily Challenge feature — every player gets the same puzzle on the same day, making it fair for comparing solve times.
The full pipeline runs in under 100 milliseconds for easy puzzles and up to a few seconds for hard puzzles (the uniqueness verification step takes longer with more cells removed). This makes real-time puzzle generation practical — we generate each puzzle on-demand when you start playing, rather than pulling from a pre-built database.
Summary: The Four Steps of Sudoku Generation
Generate a solved grid
Use backtracking with randomized digit ordering to fill all 81 cells.
Validate with constraint propagation
Check row, column, and box constraints at every placement to prune invalid paths.
Remove cells & verify uniqueness
Strategically remove cells, running a solution counter after each removal to ensure exactly one solution remains.
Grade difficulty
Control the number of removed cells to target easy (~40), medium (~50), or hard (~56) difficulty levels.
This algorithm is both elegant and practical. It guarantees that every puzzle is solvable, unique, and appropriately challenging. And because it runs entirely in TypeScript, it works seamlessly in both server-side Node.js and browser environments.
Want to learn how to solve the puzzles this algorithm creates? Check out our complete solving guide or brush up on the fundamentals with our sudoku rules page. Or, better yet — experience the algorithm in action by playing a free puzzle right now.
👑
See the Algorithm in Action
Every Sudoku King puzzle is generated by the algorithm you just learned about. Jump in and experience the result — a perfectly crafted puzzle with exactly one solution, waiting for you.
Secure checkout via Stripe • Instant delivery • No account needed
Related Reading
Best Sudoku Strategies for Beginners →
Master naked singles, hidden singles, pencil marking, and scanning techniques to solve any sudoku puzzle with pure logic.
The History of Sudoku: From Ancient Puzzles to Modern Obsession →
Who invented sudoku? Trace the puzzle from Euler’s Latin squares through Howard Garns’ “Number Place” to the global craze of the 2000s.