Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Backtracking & State Search

Builds on Constraint Backtracking, which you haven’t finished yet.

P3HardBuilt on Subsets~6 min speedrun

Find One Solution and Stop (Sudoku Solver, Word Search)

Built on
Constraint BacktrackingThe search returns True as soon as one full solution exists, and every caller returns at once, so the rest of the tree is skipped.
The problem

Sudoku Solver (LC 37)(opens leetcode.com)

Fill in every empty cell of a 9x9 Sudoku board so the finished board is a valid solution.

A board is valid when, in every row, in every column, and in each of the nine 3x3 sub-boxes, the digits 1 through 9 each appear exactly once.

Empty cells are marked with the '.' character.

Example 1
Input:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
Output:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
The input board is filled in-place so every row, column, and 3x3 box contains 1-9 exactly once.
53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79

Find One Solution

empty cell ➔ try each digit ➔ return True at once, undo only after False
Spot it when: One valid arrangement is enough, or the question is whether one exists: Sudoku, Word Search, a path on a board.
Find One Solution
Python
1def solveSudoku(board: list[list[str]]) -> list[list[str]]:
2 """Sudoku Solver (LC 37): fill every "." cell so that each row, column and 3 x 3 box holds 1 to 9 once.
3 One solved board is enough, so backtrack returns True as soon as every cell is filled and the search stops.
4 E = the number of empty cells. is_valid scans 27 cells, which is a constant.
5 Time: O(9^E) | Space: O(E) recursion stack, and the board is changed in place
6 """
7 def is_valid(r, c, ch):
8 # Check Row and Column:
9 # A digit already in this row or this column at any position
10 # would repeat, so ch is rejected if it matches either.
11 for i in range(9):
12 if board[r][i] == ch or board[i][c] == ch:
13 return False
14 
15 # Check the 3x3 Box:
16 # br, bc is the box's top-left corner; scanning its 9 cells
17 # catches a repeat the row and column checks alone would miss.
18 br = r - r % 3
19 bc = c - c % 3
20 for bi in range(br, br + 3):
21 for bj in range(bc, bc + 3):
22 if board[bi][bj] == ch:
23 return False
24 return True
25 
26 def backtrack():
27 for r in range(9):
28 for c in range(9):
29 # 1. Find the Next Empty Cell:
30 # Scanning row by row finds the first "." cell still to
31 # fill; once no empty cell remains, the board is solved.
32 if board[r][c] == ".":
33 for d in "123456789":
34 # 2. Choose, Explore, Un-choose:
35 # Try digit d only if is_valid allows it; place
36 # it and recurse, keeping it only if the rest of
37 # the board can still be solved, otherwise undo
38 # and try the next digit.
39 if is_valid(r, c, d):
40 board[r][c] = d
41 if backtrack():
42 return True
43 board[r][c] = "."
44 
45 # 3. Fail This Cell:
46 # No digit 1-9 fits here given the current board, so
47 # this whole branch is a dead end.
48 return False
49 return True
50 
51 backtrack()
52 return board
Invariant: backtrack() returns True only when every cell is filled validly, and returns False with the board exactly as it was when it was called.

Make backtrack return a boolean, and return True the moment every cell is filled. Every caller writes if backtrack(): return True, so the first success is passed up through all the calls and the search stops. The undo board[r][c] = "." runs only after a call returned False, so a failed branch leaves the board as it found it. The trap is writing backtrack() alone: the value True is lost, the undo runs after the solution was found, and it erases the solved board.

See it run

Word Search (LC 79) for ABCCED: one True stops the search

idx - · path - · found - · exist -

idx 0 · path A · found - · exist -

idx 0 · path A · found - · exist -

idx 1 · path AB · found - · exist -

idx 1 · path AB · found - · exist -

idx 2 · path ABC · found - · exist -

idx 3 · path ABCC · found - · exist -

idx 4 · path ABCCE · found - · exist -

idx 4 · path ABCCE · found - · exist -

idx 4 · path ABCCE · found - · exist -

idx 5 · path ABCCED · found True · exist -

idx 5 · path ABCCE · found True · exist -

idx 4 · path ABCC · found True · exist -

idx 3 · path ABC · found True · exist -

idx 2 · path AB · found True · exist -

idx 1 · path A · found True · exist -

idx 0 · path - · found True · exist -

idx - · path - · found True · exist True

Each cell of the table is one cell of the grid. The replay follows the Solve problem, Word Search (LC 79), which uses the same rule as Sudoku Solver (LC 37): the first True is passed up and the other calls are skipped.

  • now the cell that the current call is working on
  • path a cell on the path of the current call stack
  • rejected a cell that fails: the wrong letter, or already on the path
  • restored a cell that just got its letter back as its call returned
  • skipped a neighbour that is never tried because an earlier neighbour returned True

Start: the word is ABCCED and no cell is on the path. exist tries each cell as the first letter.

A matches word[0] at (0, 0), so the cell joins the path. The four neighbours are tried next for B.

S at (1, 0) is not B, so this call returns False and the next neighbour is tried.

B matches word[1] at (0, 1), so the cell joins the path. The four neighbours are tried next for C.

F at (1, 1) is not C, so this call returns False and the next neighbour is tried.

C matches word[2] at (0, 2), so the cell joins the path. The four neighbours are tried next for C.

C matches word[3] at (1, 2), so the cell joins the path. The four neighbours are tried next for E.

E matches word[4] at (2, 2), so the cell joins the path. The four neighbours are tried next for D.

(1, 2) is already on the path, so this call returns False and the next neighbour is tried.

E at (2, 3) is not D, so this call returns False and the next neighbour is tried.

D matches at (2, 1) and it is the last letter. The next call has idx = 6, which equals len(word), so it returns True.

(2, 1) puts its letter back and returns True.

(2, 2) puts its letter back and returns True.

(1, 2) puts its letter back and returns True. The True ends the neighbour calls, so (1, 3) and (1, 1) are never tried.

(0, 2) puts its letter back and returns True. The True ends the neighbour calls, so (0, 3) is never tried.

(0, 1) puts its letter back and returns True.

(0, 0) puts its letter back and returns True.

Done: the search from (0, 0) returned True, so exist returns True. The other 11 start cells are never tried.

row 0
col 0A
col 1B
col 2C
col 3E
row 1
S
F
C
S
row 2
A
D
E
E
1 / 18
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Ignoring the value that backtrack() returns

    Mistake
    backtrack()
    Fix
    if backtrack():
        return True

    What happens: backtrack() returns True when every cell is filled. If the call is written alone, that value is thrown away. The line board[r][c] = "." then runs and erases the digit that was just placed. Every caller above does the same on the way back, so the search takes the solved board apart and ends with the empty cells it started with.

    Example: A solved board with 5 cells made empty: row 0 columns 2 and 3, row 1 column 1, row 4 column 4 and row 8 column 8 (the call counts the cells that are still empty in the result)
    Mistake: returns 5Fix: returns 0
  2. 2

    Gotcha 2: Forgetting return False after the digit loop

    Mistake
    # nothing after the digit loop
    Fix
    return False

    What happens: When no digit fits a cell, backtrack() must return False, so the caller undoes its own digit and tries the next one. Without return False, the call goes on to the next empty cell with a cell that has no valid digit. The search then goes on to fill the rest of the board and reports success while some cells are still empty.

    Example: A solved board with 14 cells made empty (the call counts the cells that are still empty in the result)
    Mistake: returns 4Fix: returns 0
  3. 3

    Gotcha 3: Not putting "." back after a digit fails

    Mistake
    board[r][c] = d
    if backtrack():
        return True
    Fix
    board[r][c] = d
    if backtrack():
        return True
    board[r][c] = "."

    What happens: The search finds the next empty cell by looking for ".". A digit that failed stays in its cell when the undo is missing. The next scan sees a digit there and treats the cell as filled, so it never tries the other digits for that cell. The board that comes back is not a valid solution.

    Example: A solved board with 14 cells made empty (the call counts the cells that are still empty in the result)
    Mistake: returns 6Fix: returns 0
Not this one if…
  • Every complete board is wanted, as in N-Queens (LC 51), so the search never stops early: backtrack returns nothing, and the loop goes on after each board is recorded. See Constraint Backtracking
  • Every ordering is wanted, as in Permutations (LC 46), so there is no True to pass up and the search visits the whole tree. See Permutations

Deep Dive

Why the first True stops every caller

A search that needs only one solution can end when it has one. To do that, backtrack returns a boolean. It returns True when every cell is filled, and False when no digit fits the current cell.

Every caller writes if backtrack(): return True. The first True is passed up through all the calls, one return True after another. No caller tries another digit after that.

The board above replays Word Search (LC 79), the problem of the Solve stage of this card, because a 9 x 9 Sudoku board is too large to draw. It does not replay the Sudoku code of this card. It shows the same stop-early rule on a smaller problem. The four neighbour calls are joined with or, and or stops at the first True.

For the word ABCCED, the path A, B, C, C, E, D is found at step 11. Four neighbours were rejected before that: the S at (1, 0), the F at (1, 1), the cell (1, 2) that was already on the path, and the E at (2, 3). At step 14, the cells (1, 3) and (1, 1) are never tried, because (1, 2) already returned True. At the end, the other 11 start cells are never tried either.

Why the undo runs only after False

board[r][c] = d writes a digit, and board[r][c] = "." takes it back. A failed branch must leave the board as it found it, so the next digit starts from the same board. This is the invariant of the card.

A successful branch must not be undone, because the board is the answer. The line return True comes before the undo line, so a True skips the undo.

Suppose the call is written as backtrack() alone. The True is thrown away, and the undo line runs. Take a solved board with 5 empty cells: backtrack is called 6 times and never returns False. With the mistake, the undo erases all 5 digits on the way back, and solveSudoku returns the input again, with its 5 empty cells.

A dead end is the other case. When no digit from 1 to 9 fits the cell, the call returns False. Its caller then undoes its own digit and tries the next one. For a board with 14 empty cells, backtrack is called 20 times, and 5 of those calls return False.

The cost, and what happens with no solution

Each empty cell has at most 9 digits to try, so the time is O(9^E) for E empty cells. is_valid reads 27 cells, which is a constant. The stack holds one call for each empty cell, so the space is O(E).

The bound is loose, because is_valid cuts most digits at once. On the first example of Sudoku Solver (LC 37), which has 51 empty cells, backtrack is called 4,209 times. Of those calls, 4,157 return False and 52 return True.

If the board has no solution, no call ever returns True. The search visits the whole tree and returns False. Every digit was undone on the way, so the board is exactly the board that was given.

If the question asks for every solution, this card does not apply. N-Queens (LC 51) records every board and goes on after each one.

60-Second Pitch

"I make backtrack return a boolean. It finds the first empty cell and tries the digits 1 to 9. A digit is kept only if is_valid accepts it. If the recursive call returns True, I return True at once, so the solution stays on the board. If it returns False, I write "." back and try the next digit. If no digit fits, I return False. The time is O(9^E) for E empty cells, and the stack is O(E) deep."

Common Questions

Why does backtrack return a boolean and not a list of boards?

The question needs one solved board, and the board is changed in place. The boolean tells each caller whether the rest of the board could be filled. The first True ends the search, so there is no list to build.

Why is the undo skipped after a True?

The board that holds the solution is the answer. An undo would write "." over a digit that belongs to the solution. The return True line comes before the undo line, so a True leaves the board as it is.

What happens if the board has no solution?

No call returns True, so the search visits the whole tree and the first call returns False. Every digit was undone on the way back, so the board is the same as the input.

How is this different from N-Queens (LC 51)?

N-Queens (LC 51) asks for every board, so backtrack returns nothing and the loop goes on after each board is recorded. Here one board is enough, so backtrack returns a boolean and the first True ends the search.

What is the time and space complexity?

O(9^E) time for E empty cells, because each empty cell has at most 9 digits and is_valid cuts most of them at once. The space is O(E) for the recursion stack. The board is changed in place.

SWE Reasoning

Spot it

  • The statement asks whether a valid arrangement exists, or asks for any one valid arrangement.
  • Any valid answer is accepted, so there is no list of answers to build.
  • A board or a string is filled one position at a time, and each placement can be checked at once.
  • The statement says "return true if" or "fill the board in place", and not "return all".

The invariant, step by step

  1. backtrack() scans row by row for the first empty cell. If there is none, every cell is filled, and every digit passed is_valid when it was placed. So it returns True.
  2. Otherwise the loop tries each digit d from 1 to 9 in that cell. is_valid skips a digit that already appears in the row, the column or the 3 x 3 box.
  3. For a valid digit, board[r][c] = d writes it, and backtrack() fills the rest of the board.
  4. If that call returned True, the caller returns True at once and does not undo. The board now holds the solution.
  5. If that call returned False, the caller writes "." back and tries the next digit. The board is the same as before step 3.
  6. If no digit works, the call returns False with the board exactly as it was when the call started.

Traps

  • Ignoring the return value. backtrack() alone throws the True away, and the undo erases the solution. For the board with 5 empty cells, the result is the input again.
  • Forgetting return False after the digit loop. A cell with no valid digit does not stop the call. The call goes on to the next empty cell, and it reports success while some cells are still empty.
  • Not putting "." back after a digit fails. The failed digit stays in the cell, and the next scan treats the cell as filled. For the board with 14 empty cells, the result is not a valid solution.
  • Using this card when every solution is needed. A True would stop the search at the first board, and the other boards would never be found.

Practice

Canonical LeetCode:Sudoku Solver (LC 37)Word Search (LC 79)Matchsticks to Square (LC 473)Partition to K Equal Sum Subsets (LC 698)Split Array into Fibonacci Sequence (LC 842)Additive Number (LC 306)
Theory Context•Backtracking & State Search

Builds on Constraint Backtracking, which you haven’t finished yet.

P3HardBuilt on Subsets~6 min speedrun

Find One Solution and Stop (Sudoku Solver, Word Search)

Built on
Constraint BacktrackingThe search returns True as soon as one full solution exists, and every caller returns at once, so the rest of the tree is skipped.
The problem

Sudoku Solver (LC 37)(opens leetcode.com)

Fill in every empty cell of a 9x9 Sudoku board so the finished board is a valid solution.

A board is valid when, in every row, in every column, and in each of the nine 3x3 sub-boxes, the digits 1 through 9 each appear exactly once.

Empty cells are marked with the '.' character.

Example 1
Input:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
Output:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
The input board is filled in-place so every row, column, and 3x3 box contains 1-9 exactly once.
53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79

Find One Solution

empty cell ➔ try each digit ➔ return True at once, undo only after False
Spot it when: One valid arrangement is enough, or the question is whether one exists: Sudoku, Word Search, a path on a board.
Find One Solution
Python
1def solveSudoku(board: list[list[str]]) -> list[list[str]]:
2 """Sudoku Solver (LC 37): fill every "." cell so that each row, column and 3 x 3 box holds 1 to 9 once.
3 One solved board is enough, so backtrack returns True as soon as every cell is filled and the search stops.
4 E = the number of empty cells. is_valid scans 27 cells, which is a constant.
5 Time: O(9^E) | Space: O(E) recursion stack, and the board is changed in place
6 """
7 def is_valid(r, c, ch):
8 # Check Row and Column:
9 # A digit already in this row or this column at any position
10 # would repeat, so ch is rejected if it matches either.
11 for i in range(9):
12 if board[r][i] == ch or board[i][c] == ch:
13 return False
14 
15 # Check the 3x3 Box:
16 # br, bc is the box's top-left corner; scanning its 9 cells
17 # catches a repeat the row and column checks alone would miss.
18 br = r - r % 3
19 bc = c - c % 3
20 for bi in range(br, br + 3):
21 for bj in range(bc, bc + 3):
22 if board[bi][bj] == ch:
23 return False
24 return True
25 
26 def backtrack():
27 for r in range(9):
28 for c in range(9):
29 # 1. Find the Next Empty Cell:
30 # Scanning row by row finds the first "." cell still to
31 # fill; once no empty cell remains, the board is solved.
32 if board[r][c] == ".":
33 for d in "123456789":
34 # 2. Choose, Explore, Un-choose:
35 # Try digit d only if is_valid allows it; place
36 # it and recurse, keeping it only if the rest of
37 # the board can still be solved, otherwise undo
38 # and try the next digit.
39 if is_valid(r, c, d):
40 board[r][c] = d
41 if backtrack():
42 return True
43 board[r][c] = "."
44 
45 # 3. Fail This Cell:
46 # No digit 1-9 fits here given the current board, so
47 # this whole branch is a dead end.
48 return False
49 return True
50 
51 backtrack()
52 return board
Invariant: backtrack() returns True only when every cell is filled validly, and returns False with the board exactly as it was when it was called.

Make backtrack return a boolean, and return True the moment every cell is filled. Every caller writes if backtrack(): return True, so the first success is passed up through all the calls and the search stops. The undo board[r][c] = "." runs only after a call returned False, so a failed branch leaves the board as it found it. The trap is writing backtrack() alone: the value True is lost, the undo runs after the solution was found, and it erases the solved board.

See it run

Word Search (LC 79) for ABCCED: one True stops the search

idx - · path - · found - · exist -

idx 0 · path A · found - · exist -

idx 0 · path A · found - · exist -

idx 1 · path AB · found - · exist -

idx 1 · path AB · found - · exist -

idx 2 · path ABC · found - · exist -

idx 3 · path ABCC · found - · exist -

idx 4 · path ABCCE · found - · exist -

idx 4 · path ABCCE · found - · exist -

idx 4 · path ABCCE · found - · exist -

idx 5 · path ABCCED · found True · exist -

idx 5 · path ABCCE · found True · exist -

idx 4 · path ABCC · found True · exist -

idx 3 · path ABC · found True · exist -

idx 2 · path AB · found True · exist -

idx 1 · path A · found True · exist -

idx 0 · path - · found True · exist -

idx - · path - · found True · exist True

Each cell of the table is one cell of the grid. The replay follows the Solve problem, Word Search (LC 79), which uses the same rule as Sudoku Solver (LC 37): the first True is passed up and the other calls are skipped.

  • now the cell that the current call is working on
  • path a cell on the path of the current call stack
  • rejected a cell that fails: the wrong letter, or already on the path
  • restored a cell that just got its letter back as its call returned
  • skipped a neighbour that is never tried because an earlier neighbour returned True

Start: the word is ABCCED and no cell is on the path. exist tries each cell as the first letter.

A matches word[0] at (0, 0), so the cell joins the path. The four neighbours are tried next for B.

S at (1, 0) is not B, so this call returns False and the next neighbour is tried.

B matches word[1] at (0, 1), so the cell joins the path. The four neighbours are tried next for C.

F at (1, 1) is not C, so this call returns False and the next neighbour is tried.

C matches word[2] at (0, 2), so the cell joins the path. The four neighbours are tried next for C.

C matches word[3] at (1, 2), so the cell joins the path. The four neighbours are tried next for E.

E matches word[4] at (2, 2), so the cell joins the path. The four neighbours are tried next for D.

(1, 2) is already on the path, so this call returns False and the next neighbour is tried.

E at (2, 3) is not D, so this call returns False and the next neighbour is tried.

D matches at (2, 1) and it is the last letter. The next call has idx = 6, which equals len(word), so it returns True.

(2, 1) puts its letter back and returns True.

(2, 2) puts its letter back and returns True.

(1, 2) puts its letter back and returns True. The True ends the neighbour calls, so (1, 3) and (1, 1) are never tried.

(0, 2) puts its letter back and returns True. The True ends the neighbour calls, so (0, 3) is never tried.

(0, 1) puts its letter back and returns True.

(0, 0) puts its letter back and returns True.

Done: the search from (0, 0) returned True, so exist returns True. The other 11 start cells are never tried.

row 0
col 0A
col 1B
col 2C
col 3E
row 1
S
F
C
S
row 2
A
D
E
E
1 / 18
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Ignoring the value that backtrack() returns

    Mistake
    backtrack()
    Fix
    if backtrack():
        return True

    What happens: backtrack() returns True when every cell is filled. If the call is written alone, that value is thrown away. The line board[r][c] = "." then runs and erases the digit that was just placed. Every caller above does the same on the way back, so the search takes the solved board apart and ends with the empty cells it started with.

    Example: A solved board with 5 cells made empty: row 0 columns 2 and 3, row 1 column 1, row 4 column 4 and row 8 column 8 (the call counts the cells that are still empty in the result)
    Mistake: returns 5Fix: returns 0
  2. 2

    Gotcha 2: Forgetting return False after the digit loop

    Mistake
    # nothing after the digit loop
    Fix
    return False

    What happens: When no digit fits a cell, backtrack() must return False, so the caller undoes its own digit and tries the next one. Without return False, the call goes on to the next empty cell with a cell that has no valid digit. The search then goes on to fill the rest of the board and reports success while some cells are still empty.

    Example: A solved board with 14 cells made empty (the call counts the cells that are still empty in the result)
    Mistake: returns 4Fix: returns 0
  3. 3

    Gotcha 3: Not putting "." back after a digit fails

    Mistake
    board[r][c] = d
    if backtrack():
        return True
    Fix
    board[r][c] = d
    if backtrack():
        return True
    board[r][c] = "."

    What happens: The search finds the next empty cell by looking for ".". A digit that failed stays in its cell when the undo is missing. The next scan sees a digit there and treats the cell as filled, so it never tries the other digits for that cell. The board that comes back is not a valid solution.

    Example: A solved board with 14 cells made empty (the call counts the cells that are still empty in the result)
    Mistake: returns 6Fix: returns 0
Not this one if…
  • Every complete board is wanted, as in N-Queens (LC 51), so the search never stops early: backtrack returns nothing, and the loop goes on after each board is recorded. See Constraint Backtracking
  • Every ordering is wanted, as in Permutations (LC 46), so there is no True to pass up and the search visits the whole tree. See Permutations

Deep Dive

Why the first True stops every caller

A search that needs only one solution can end when it has one. To do that, backtrack returns a boolean. It returns True when every cell is filled, and False when no digit fits the current cell.

Every caller writes if backtrack(): return True. The first True is passed up through all the calls, one return True after another. No caller tries another digit after that.

The board above replays Word Search (LC 79), the problem of the Solve stage of this card, because a 9 x 9 Sudoku board is too large to draw. It does not replay the Sudoku code of this card. It shows the same stop-early rule on a smaller problem. The four neighbour calls are joined with or, and or stops at the first True.

For the word ABCCED, the path A, B, C, C, E, D is found at step 11. Four neighbours were rejected before that: the S at (1, 0), the F at (1, 1), the cell (1, 2) that was already on the path, and the E at (2, 3). At step 14, the cells (1, 3) and (1, 1) are never tried, because (1, 2) already returned True. At the end, the other 11 start cells are never tried either.

Why the undo runs only after False

board[r][c] = d writes a digit, and board[r][c] = "." takes it back. A failed branch must leave the board as it found it, so the next digit starts from the same board. This is the invariant of the card.

A successful branch must not be undone, because the board is the answer. The line return True comes before the undo line, so a True skips the undo.

Suppose the call is written as backtrack() alone. The True is thrown away, and the undo line runs. Take a solved board with 5 empty cells: backtrack is called 6 times and never returns False. With the mistake, the undo erases all 5 digits on the way back, and solveSudoku returns the input again, with its 5 empty cells.

A dead end is the other case. When no digit from 1 to 9 fits the cell, the call returns False. Its caller then undoes its own digit and tries the next one. For a board with 14 empty cells, backtrack is called 20 times, and 5 of those calls return False.

The cost, and what happens with no solution

Each empty cell has at most 9 digits to try, so the time is O(9^E) for E empty cells. is_valid reads 27 cells, which is a constant. The stack holds one call for each empty cell, so the space is O(E).

The bound is loose, because is_valid cuts most digits at once. On the first example of Sudoku Solver (LC 37), which has 51 empty cells, backtrack is called 4,209 times. Of those calls, 4,157 return False and 52 return True.

If the board has no solution, no call ever returns True. The search visits the whole tree and returns False. Every digit was undone on the way, so the board is exactly the board that was given.

If the question asks for every solution, this card does not apply. N-Queens (LC 51) records every board and goes on after each one.

60-Second Pitch

"I make backtrack return a boolean. It finds the first empty cell and tries the digits 1 to 9. A digit is kept only if is_valid accepts it. If the recursive call returns True, I return True at once, so the solution stays on the board. If it returns False, I write "." back and try the next digit. If no digit fits, I return False. The time is O(9^E) for E empty cells, and the stack is O(E) deep."

Common Questions

Why does backtrack return a boolean and not a list of boards?

The question needs one solved board, and the board is changed in place. The boolean tells each caller whether the rest of the board could be filled. The first True ends the search, so there is no list to build.

Why is the undo skipped after a True?

The board that holds the solution is the answer. An undo would write "." over a digit that belongs to the solution. The return True line comes before the undo line, so a True leaves the board as it is.

What happens if the board has no solution?

No call returns True, so the search visits the whole tree and the first call returns False. Every digit was undone on the way back, so the board is the same as the input.

How is this different from N-Queens (LC 51)?

N-Queens (LC 51) asks for every board, so backtrack returns nothing and the loop goes on after each board is recorded. Here one board is enough, so backtrack returns a boolean and the first True ends the search.

What is the time and space complexity?

O(9^E) time for E empty cells, because each empty cell has at most 9 digits and is_valid cuts most of them at once. The space is O(E) for the recursion stack. The board is changed in place.

SWE Reasoning

Spot it

  • The statement asks whether a valid arrangement exists, or asks for any one valid arrangement.
  • Any valid answer is accepted, so there is no list of answers to build.
  • A board or a string is filled one position at a time, and each placement can be checked at once.
  • The statement says "return true if" or "fill the board in place", and not "return all".

The invariant, step by step

  1. backtrack() scans row by row for the first empty cell. If there is none, every cell is filled, and every digit passed is_valid when it was placed. So it returns True.
  2. Otherwise the loop tries each digit d from 1 to 9 in that cell. is_valid skips a digit that already appears in the row, the column or the 3 x 3 box.
  3. For a valid digit, board[r][c] = d writes it, and backtrack() fills the rest of the board.
  4. If that call returned True, the caller returns True at once and does not undo. The board now holds the solution.
  5. If that call returned False, the caller writes "." back and tries the next digit. The board is the same as before step 3.
  6. If no digit works, the call returns False with the board exactly as it was when the call started.

Traps

  • Ignoring the return value. backtrack() alone throws the True away, and the undo erases the solution. For the board with 5 empty cells, the result is the input again.
  • Forgetting return False after the digit loop. A cell with no valid digit does not stop the call. The call goes on to the next empty cell, and it reports success while some cells are still empty.
  • Not putting "." back after a digit fails. The failed digit stays in the cell, and the next scan treats the cell as filled. For the board with 14 empty cells, the result is not a valid solution.
  • Using this card when every solution is needed. A True would stop the search at the first board, and the other boards would never be found.

Practice

Canonical LeetCode:Sudoku Solver (LC 37)Word Search (LC 79)Matchsticks to Square (LC 473)Partition to K Equal Sum Subsets (LC 698)Split Array into Fibonacci Sequence (LC 842)Additive Number (LC 306)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•