DEV Community

Timevolt
Timevolt

Posted on

Backtracking: The Sudoku Strikes Back

The Quest Begins (The "Why")

Honestly, I used to stare at a blank Sudoku grid and feel like I was trying to solve a Rubik’s Cube blindfolded. I’d fill in a few obvious numbers, then hit a wall where every guess felt like a shot in the dark. The same frustration popped up when I tackled the N‑Queens problem in an interview: place eight queens on a chessboard so none attack each other. My first attempt was a naïve brute‑force loop that tried every permutation, and it choked on anything beyond N = 5.

I kept asking myself: Why does trying every possibility feel so wasteful? The answer was hiding in plain sight: most of those branches are dead ends before we even finish placing a single piece. If we could detect a dead end early and backtrack, we’d prune huge‑of‑search‑space would never be explored. That’s the moment backtracking stopped being a textbook curiosity and felt like a real super‑power.

The Revelation (The Insight)

Backtracking isn’t just “try something, undo it if it fails.” It’s a depth‑first search that prunes the search tree the instant we know a partial solution can’t possibly lead to a valid answer. The magic lies in the validation step: before we go deeper, we ask, “Does the current placement break any rule?” If yes, we retreat immediately—no need to explore all the children of that node.

Think of it like playing a game of chess where you can see that moving a pawn will expose your king to check; you don’t waste time contemplating the rest of the board, you just undo the move and try another. That early‑exit check turns an exponential nightmare into something tractable for many puzzles because the branching factor drops dramatically once constraints are applied.

In Sudoku, each cell has at most nine candidates, but the row, column, and 3×3 block constraints cut that down fast. In N‑Queens, placing a queen eliminates an entire row, column, and two diagonals, so the number of safe squares shrinks quickly as we go deeper. The algorithm’s correctness hinges on two simple invariants:

  1. Partial validity – every placed piece respects all constraints.
  2. Exhaustiveness – we systematically try every candidate for the next empty cell, backtracking when none work.

If a complete board ever satisfies the constraints, we’ll find it; if none exists, we’ll have explored every viable path and can confidently report failure.

Wielding the Power (Code & Examples)

The Naïve Struggle (What NOT to Do)

def solve_sudoku_bruteforce(board):
    empty = find_empty(board)
    if not empty:
        return True                      # solved
    r, c = empty
    for num in range(1, 10):             # try every number blindly
        board[r][c] = num
        if solve_sudoku_bruteforce(board):
            return True
        board[r][c] = 0                  # undo
    return False
Enter fullscreen mode Exit fullscreen mode

The problem? We never check whether num actually fits the Sudoku rules before recursing. For a moderately filled board, this can explode to millions of useless calls.

The Victory: Backtracking with Pruning

def is_valid(board, r, c, num):
    # row & column
    for i in range(9):
        if board[r][i] == num or board[i][c] == num:
            return False
    # 3×3 block
    sr, sc = 3 * (r // 3), 3 * (c // 3)
    for i in range(sr, sr + 3):
        for j in range(sc, sc + 3):
            if board[i][j] == num:
                return False
    return True

def solve_sudoku(board):
    empty = find_empty(board)
    if not empty:
        return True                      # solved!
    r, c = empty
    for num in range(1, 10):
        if is_valid(board, r, c, num):   # <-- PRUNE HERE
            board[r][c] = num
            if solve_sudoku(board):
                return True
            board[r][c] = 0              # backtrack
    return False
Enter fullscreen mode Exit fullscreen mode

Why this works: The is_valid test is O(1) (constant‑time because the board size is fixed). As soon as a number violates a rule, we skip the recursive dive entirely. In practice, the branching factor drops from 9 to often 2‑3 after a few placements, turning the worst‑case exponential search into something that solves typical puzzles in milliseconds.

Common Mistake (the “Trap”)

Forgetting to reset the cell after a failed attempt. If you leave board[r][c] = num on the board when backtracking, later validity checks will see a phantom number and incorrectly prune valid solutions. Always undo the move—think of it as putting the game piece back in its box before trying the next one.

N‑Queens: Same Pattern, Different Board

def solve_nqueens(n):
    cols = [0] * n          # cols[r] = column where queen in row r sits
    diag1 = [0] * (2 * n)   # r + c
    diag2 = [0] * (2 * n)   # r - c + n - 1

    def backtrack(row):
        if row == n:
            return True                     # all queens placed
        for col in range(n):
            d1 = row + col
            d2 = row - col + n - 1
            if cols[col] or diag1[d1] or diag2[d2]:
                continue                    # <-- PRUNE
            # place queen
            cols[col] = diag1[d1] = diag2[d2] = 1
            if backtrack(row + 1):
                return True
            # remove queen (backtrack)
            cols[col] = diag1[d1] = diag2[d2] = 0
        return False

    return backtrack(0)
Enter fullscreen mode Exit fullscreen mode

Here the pruning checks three bit‑like arrays that instantly tell us if a column or diagonal is already occupied. The algorithm explores at most n! leaf nodes in the worst case, but the pruning cuts the search space dramatically—solving N = 14 in a fraction of a second on a laptop.

Why This New Power Matters

Armed with backtracking, you can turn a “try everything and hope” interview question into a confident, elegant solution. You’ll notice the pattern everywhere: constraint satisfaction puzzles, graph coloring, even certain parsing tasks. The key takeaway isn’t the code itself—it’s the mindset: validate early, backtrack fast, and let the constraints do the heavy lifting.

When you walk into your next interview and the interviewer drops a Sudoku or N‑Queens variant, you’ll smile, reach for the pruning check, and watch the solution fall into place like a perfectly timed combo in a fighting game.

Your Turn

Grab a Sudoku puzzle from the newspaper (or generate one online) and implement the solver above. Then, try tweaking the pruning: what happens if you reorder the numbers you try based on how often they appear in the row/column/block? Does it speed things up? Share your results or a new twist in the comments—I’d love to see how you level‑up the quest!

Happy backtracking! 🚀

Top comments (0)