DEV Community

Timevolt
Timevolt

Posted on

Backtracking: The Sudoku Matrix

The Quest Begins (The “Why”)

I remember the first time I stared at a blank Sudoku grid during a mock interview and felt my brain short‑circuit. The interviewer asked, “Can you write a solver that fills in any valid puzzle?” I tried the obvious approach: nested loops that guessed a number, moved to the next cell, and backtracked only when I hit a dead end. The code was a mess of indices, and after twenty minutes of debugging I realized I was essentially trying every combination blindly — like attempting to open a combination lock by twisting each dial randomly and hoping for the best. It was frustrating, and I knew there had to be a smarter way to explore the space without losing my mind.

That moment sparked my quest for a technique that could intelligently trial‑and‑error, keep track of what’s been tried, and retreat gracefully when a path leads nowhere. Enter backtracking — the algorithm that turns brute force guessing into a principled search.

The Revelation (The Insight)

At its core, backtracking is just depth‑first search with a safety net. Imagine you’re navigating a maze. You pick a direction, walk forward, and if you hit a wall you don’t start over from the entrance; you simply step back to the last junction and try a different path. That “step back” is the undo operation, and it’s what makes backtracking efficient: you never re‑examine a state you’ve already proven useless.

Why does this guarantee a solution (if one exists)? Because the algorithm systematically enumerates every possible assignment that respects the constraints, pruning branches the moment a constraint is violated. If a solution exists, there is a leaf node in this search tree that satisfies all constraints, and the algorithm will eventually reach it. If no solution exists, the tree is exhausted and the algorithm reports failure.

The magic lies in the pruning step. In Sudoku, as soon as you place a number that repeats in a row, column, or 3×3 block, you know that entire subtree is fruitless and can backtrack immediately. In N‑Queens, placing two queens on the same diagonal kills the whole branch. By cutting off dead ends early, we avoid exploring an astronomical number of useless combinations.

Wielding the Power (Code & Examples)

The Struggle: A Naïve Attempt

def solve_sudoku_bruteforce(board):
    # tries every number 1‑9 in every empty cell, no pruning
    empty = find_empty(board)
    if not empty:
        return True
    r, c = empty
    for num in range(1, 10):
        board[r][c] = num
        if solve_sudoku_bruteforce(board):
            return True
        board[r][c] = 0          # reset, but we never checked constraints early
    return False
Enter fullscreen mode Exit fullscreen mode

This works, but it wastes time checking invalid placements deep inside the recursion because we never validate the move before recursing.

The Victory: Clean Backtracking with Pruning

def solve_sudoku(board):
    empty = find_empty(board)
    if not empty:                     # puzzle filled → success
        return True

    r, c = empty
    for num in range(1, 10):
        if is_valid(board, r, c, num):   # <-- prune *before* diving deeper
            board[r][c] = num
            if solve_sudoku(board):
                return True
            board[r][c] = 0              # undo the choice (backtrack)
    return False                         # trigger backtracking to previous level

def is_valid(board, r, c, num):
    # row & column check
    for i in range(9):
        if board[r][i] == num or board[i][c] == num:
            return False
    # 3×3 block check
    br, bc = 3 * (r // 3), 3 * (c // 3)
    for i in range(br, br + 3):
        for j in range(bc, bc + 3):
            if board[i][j] == num:
                return False
    return True
Enter fullscreen mode Exit fullscreen mode

What changed?

  1. Early validationis_valid rejects a move instantly if it breaks any Sudoku rule.
  2. Explicit undo – after the recursive call we reset the cell, ensuring the parent level sees a clean state.
  3. Clear termination – when find_empty returns None, we’ve hit a solved board and unwind the recursion with True.

The same pattern solves N‑Queens with barely any tweaks:

def solve_nqueens(n):
    board = [-1] * n          # board[row] = col where queen sits
    def backtrack(row):
        if row == n:          # all rows filled → solution found
            return True
        for col in range(n):
            if is_safe(board, row, col):
                board[row] = col
                if backtrack(row + 1):
                    return True
                board[row] = -1   # undo
        return False
    return backtrack(0)

def is_safe(board, row, col):
    for r in range(row):
        c = board[r]
        if c == col or abs(c - col) == row - r:   # same column or diagonal
            return False
    return True
Enter fullscreen mode Exit fullscreen mode

Notice how the pruning (is_safe) kills entire sub‑trees the moment a queen would attack another.

Common Traps to Avoid

  • Forgetting to undo – leaving a stale value in the board causes the parent level to see an invalid state and leads to missed solutions.
  • Checking validity too late – validating after the recursive call means you’ve already gone down a useless path, wasting time.
  • Using mutable defaults or globals – they leak state between recursive calls and make reasoning about the algorithm harder.

Why This New Power Matters

Armed with backtracking, you’ve gained a versatile tool that appears in countless interview puzzles: Sudoku solvers, N‑Queens, word searches, graph coloring, even configuring feature toggles. The technique teaches you to think in terms of state and constraints rather than brute force loops.

From a complexity standpoint, the worst case remains exponential — O(b^d) where b is the branching factor (choices per cell) and d is the depth (number of decisions). For Sudoku, b ≤ 9 and d equals the number of empty cells, giving a theoretical bound of O(9^m). In practice, the pruning cuts the effective branching factor dramatically; typical puzzles solve in milliseconds. For N‑Queens, the naïve bound is O(N!), but pruning reduces the explored nodes to a fraction of that, making N = 14 feasible in a blink.

What’s truly empowering is the mindset shift: when you encounter a problem that asks “does there exist a configuration satisfying X?”, you immediately sketch a backtracking scaffold, define the validity test, and let the recursion do the heavy lifting.

Your Turn

Try this: take the Sudoku solver above and adapt it to solve Killer Sudoku (where cages have sum constraints). Or, for a twist, implement N‑Queens using bit‑masking to represent columns, diagonals, and anti‑diagonals — watch how the pruning becomes almost instantaneous.

When you get it working, drop a comment with your time‑taken or a screenshot of the solved board. I’m excited to see what you’ll build next!

Happy backtracking! 🚀

Top comments (0)