Recursion & Backtracking · Recursion & Backtracking Fundamentals

Grid Path Backtracking

Exploring every route through a grid from a start cell to a destination, trying moves in a fixed priority order and marking cells visited for the current path only - so a path can revisit a cell after backing away from a dead end elsewhere.

  • 6 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: tracing a word
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

"Every route through the maze" and "can this word be traced through adjacent letters?" walk a grid like a graph traversal, with one crucial difference. A shortest-path BFS visits each cell once, ever. Listing paths must let different paths reuse the same cell; only a single path can't cross itself. So the visited marks belong to the current path: mark a cell when you step onto it, and unmark it when you step back.

We'll go from simple to more technical:

  1. The pattern: mark on entry, explore four directions, unmark on exit.
  2. A second flavor: tracing a word, using the board itself as the mark.
  3. Where path search shows up in production (word games, routing puzzles).
  4. What breaks it: never unmarking, never marking, and direction order.

The approach

Java

void backtrack(int r, int c, boolean[][] visited, StringBuilder path, List<String> result) {
    if (r == n - 1 && c == n - 1) { result.add(path.toString()); return; }
    visited[r][c] = true;                                    // on the current path
    for (int d = 0; d < 4; d++) {                            // D, L, R, U - a fixed order
        int nr = r + DR[d], nc = c + DC[d];
        if (inBounds(nr, nc) && maze[nr][nc] == 1 && !visited[nr][nc]) {
            path.append(MOVE[d]);
            backtrack(nr, nc, visited, path, result);
            path.deleteCharAt(path.length() - 1);            // undo the move
        }
    }
    visited[r][c] = false;                                   // off the path: free for other paths
}

Python

def backtrack(r: int, c: int, visited: list[list[bool]], path: list[str], result: list[str]) -> None:
    if r == n - 1 and c == n - 1:
        result.append("".join(path))
        return
    visited[r][c] = True                                     # on the current path
    for dr, dc, move in MOVES:                               # D, L, R, U - a fixed order
        nr, nc = r + dr, c + dc
        if in_bounds(nr, nc) and maze[nr][nc] == 1 and not visited[nr][nc]:
            path.append(move)
            backtrack(nr, nc, visited, path, result)
            path.pop()                                       # undo the move
    visited[r][c] = False                                    # off the path: free for other paths

Marking on the way in stops the current path from looping back on itself; unmarking on the way out lets a later path, reached by a different route, pass through the same cell. The fixed move order decides the order results appear: D, L, R, U is alphabetical, and since no complete path is a prefix of another (each one ends at the destination), the paths come out in lexicographic order.

A second flavor: tracing a word

Word Search asks whether a word can be spelled by moving between adjacent cells, using each cell at most once. It's the same per-path marking - usually by temporarily overwriting the cell:

Java

boolean search(char[][] board, String word, int i, int r, int c) {
    if (i == word.length()) return true;
    if (r < 0 || c < 0 || r >= board.length || c >= board[0].length || board[r][c] != word.charAt(i)) return false;
    char saved = board[r][c];
    board[r][c] = '#';                                       // used by this path
    boolean found = search(board, word, i + 1, r + 1, c) || search(board, word, i + 1, r - 1, c)
                 || search(board, word, i + 1, r, c + 1) || search(board, word, i + 1, r, c - 1);
    board[r][c] = saved;                                     // restore for other paths
    return found;
}

Python

def search(board: list[list[str]], word: str, i: int, r: int, c: int) -> bool:
    if i == len(word):
        return True
    if r < 0 or c < 0 or r >= len(board) or c >= len(board[0]) or board[r][c] != word[i]:
        return False                                         # r < 0 matters: board[-1] is the last row
    saved = board[r][c]
    board[r][c] = "#"                                        # used by this path
    found = (search(board, word, i + 1, r + 1, c) or search(board, word, i + 1, r - 1, c)
             or search(board, word, i + 1, r, c + 1) or search(board, word, i + 1, r, c - 1))
    board[r][c] = saved                                      # restore for other paths
    return found

Overwriting the cell doubles as the visited mark with no extra array; restoring it is the unmark. Short-circuiting || stops as soon as one direction succeeds (Python's or does the same). In Python the board has to be a list of lists: strings are immutable, so a row stored as a str can't be overwritten in place.

Where you'll see it

Board-game move generators (word games, puzzles), route enumeration with per-route constraints (a delivery route that may not revisit a stop), and circuit or PCB path search all walk grids with per-path state. When only one path or the shortest path matters, those systems switch to BFS or A* - enumeration is reserved for "all" or "does any exist under per-path rules".

Which tool for which job?

You needUse
Every path through a gridDFS with per-path marks: mark, explore, unmark
Can a word be traced?the same, overwriting the cell as the mark
Is there any path, or the shortest?BFS, O(rows × cols)
Many words on one boardone walk with a trie of all the words
A word that can't fitletter-count check before searching
A word with one rare lettersearch from the rarer end

Common mistakes

MistakeWhat happensDo this instead
Never unmarking12 corner-to-corner paths on 3 × 3 shrink to 2unmark when you step back
Never markinga word reuses a cell ("ABCB" with one B)mark before recursing
Unspecified direction orderright answers, wrong orderuse the order the problem gives
Enumerating to find any pathexponential workBFS
Searching each dictionary word separatelyshared prefixes are redonea trie

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. What happens if the rat-in-a-maze search never unmarks cells?

Show answer

Cells touched by an earlier path stay blocked for every later path, so most paths are never found - an open 3 x 3 grid reports 2 paths instead of 12. The mark must only last while the cell is on the current path.

2. Why does trying moves in the order D, L, R, U produce the paths in lexicographic order?

Show answer

D < L < R < U alphabetically, and DFS explores all paths starting with an earlier move before any path starting with a later one, at every step. Because each path ends at the destination, no path is a prefix of another, so this order is exactly lexicographic order.

3. How does Word Search mark cells without a visited array?

Show answer

It overwrites the current cell with a character that can't match (such as '#') before exploring neighbors, and writes the original letter back afterwards. The overwrite stops the path from reusing the cell; the restore frees it for other paths.

Key takeaways

  • Enumerating grid paths marks cells for the current path only: mark on entry, unmark on exit.
  • Never unmarking hides most paths; never marking lets a path reuse cells.
  • The fixed move order determines the output order (D, L, R, U gives lexicographic paths).
  • Word Search is the same pattern, marking by temporarily overwriting the cell.
  • Exponential for enumeration; use BFS when one or the shortest path is enough.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Every pathper-path marks
Any path / shortestBFS
A wordoverwrite the cell as the mark
Many wordsone walk with a trie
Impossible wordsletter-count check

Snippets worth knowing by heart

Java

boolean search(char[][] b, String w, int i, int r, int c) {
    if (i == w.length()) return true;
    if (r < 0 || c < 0 || r >= b.length || c >= b[0].length || b[r][c] != w.charAt(i)) return false;
    char saved = b[r][c]; b[r][c] = '#';             // mark for this path
    boolean found = search(b, w, i + 1, r + 1, c) || search(b, w, i + 1, r - 1, c)
                 || search(b, w, i + 1, r, c + 1) || search(b, w, i + 1, r, c - 1);
    b[r][c] = saved;                                  // unmark
    return found;
}

Python

need, have = Counter(word), Counter(ch for row in board for ch in row)
if any(have[ch] < k for ch, k in need.items()):
    return False
if have[word[0]] > have[word[-1]]:
    word = word[::-1]                    # start from the rarer end

The traps

  • Never unmarking: cells stay blocked for later paths, so most paths vanish. Unmark when you step back.
  • Never marking: a word reuses a cell, so "ABCB" matches with one B. Mark before recursing.
  • Direction order: the answers are right but listed in the wrong order. Use the order the problem gives.
  • Enumerating when BFS would do: finding any path by backtracking is exponential. Use BFS.

Staff-level points, one line each

  • Corner-to-corner simple paths: 12 (3 × 3), 184, 8,512, 1,262,816 (6 × 6).
  • Word with no matching B: 106,881 calls plain, rejected at once by the count check, 25 calls reversed.
  • With one B on the board: 3,971 calls forward, 42 reversed.
  • 400 words: 20,849 calls one at a time, 2,525 with a trie; both found the same 76 words.

Say it in an interview

  • "Marks belong to the current path, so I unmark on the way back."
  • "If they only need reachability, it's BFS in O(rows × cols)."
  • "For Word Search I check letter counts and start from the rarer end."
  • "For many words I'd walk the board once with a trie."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Grid Path Backtracking free

Rat in a Maze

HardProCoding problemJava · Python

Given an n x n maze where 1 means open and 0 means blocked, find every path for a rat to travel from the top-left cell (0, 0) to the bottom-right cell (n-1, n-1), moving one cell at a time in direction Down, Left, Right, or Up, never revisiting a cell within the same path, and…

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Grid Path Backtracking free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime