skip to content

questions

4

In grid path backtracking, why must a cell be un-marked after its recursive branch returns?

level: juniorimportance: must knowfreq 72%

answer

  1. what exactly does the mark claim?
  2. on this path, or seen ever?
  3. the state must describe the current path
  4. other branches still need that cell
  5. choose, explore, then un-choose

basics

~20 s

A cell is marked only to keep the path currently being built from reusing it. Once that branch returns, the cell is no longer on the path, so leaving it marked wrongly blocks every other path that needs to pass through it.

solid answer

~40 s

The mark means "this cell is on the path I am building right now", not "this cell has been seen". Backtracking is choose, explore, un-choose: marking the cell is the choice, and un-marking it as the frame returns is the un-choice that restores the state the caller assumes. Skip it and every cell a failed branch touched stays blocked, so a later branch that legitimately needs one of them reports failure — a false negative that looks exactly like a correct "not found". Worse, the grid is left dirty, so a second query against the same grid fails for reasons invisible in the code you are reading. The un-mark has to sit on every exit path out of the frame, the successful one included.

go deeper

for a junior

Be ready to say what the mark means in one sentence: this cell is on the path being built right now. Then show where the un-mark goes — after the neighbour loop, before returning.

for a middle

Explain the choose-explore-un-choose shape and demonstrate the failure it prevents: a branch that fails still poisons cells a sibling branch needs, producing a false negative rather than a crash.

for a senior

Show you would catch this in review and in tests: assert the grid is byte-for-byte unchanged after a query, and test two queries in a row against the same grid, since a single-query test passes with the bug present.

for a principal

Own the state-ownership call: whether the search mutates the caller's grid at all. A search that writes sentinels into shared input is cheap but hostile to reuse, caching and concurrent readers, and that constraint belongs in the interface, not in a comment.

## What the search is actually doing A grid path search grows a **simple path** — a chain of adjacent cells with no cell used twice. Picture a genomics-style letter grid where each cell holds one base, and you are asking whether some target sequence can be spelled by stepping from cell to adjacent cell. The recursion's state is three things: the cell you are standing on, how much of the target you have matched, and **which cells the current path already occupies**. That third piece is what the visited mark encodes. It exists because of a problem constraint ("a cell may not be reused within one path"), not because of an efficiency trick. ## The mark is a property of the path, not of the search This is the whole answer, and the whole misconception. There are two completely different meanings people attach to the word "visited": | Meaning | Marked when | Cleared when | What it buys | |---|---|---|---| | "on the path I am building right now" | entering a cell on this path | leaving that cell, as the frame returns | correctness of the no-reuse rule | | "resolved once and for all" | first time the cell is reached | never | each cell is expanded once, giving a linear bound | The second meaning belongs to a reachability sweep — you want the set of cells connected to a start, each cell's answer is final the moment you settle it, and never clearing the mark is exactly right. The first meaning belongs to path search, where a cell that failed *on this path* may be essential *on a different one*. Carrying the second discipline into the first problem is the classic bug. ## The failure, concretely Say the robot-style scan starts at the top-left cell and follows a branch three cells deep before the fourth base fails to match. Those three cells are unwound in the call stack, but if the marks stay, they are permanently off the board. A later branch — perhaps the one that actually spells the target — arrives at cell two, finds it marked, and turns away. The function returns "no such path" and every test you wrote with a single expected match still passes, because the first match is usually found before enough cells are poisoned. The second symptom is nastier: the grid is a shared, mutable object. After one query returns, the marks left behind are still there, so the *next* query against the same grid behaves differently from the first. The bug is not in the code you are staring at; it is in the state the previous call abandoned. ## Choose, explore, un-choose The discipline that prevents both symptoms is a shape you can apply mechanically: 1. **Choose** — mark the cell as on-path. 2. **Explore** — recurse into each unmarked orthogonal neighbour. 3. **Un-choose** — un-mark the cell, then return. The un-mark belongs to step 3, after the neighbour loop, and it must be reached on **every** way out of the frame. The most common leak is an early `return true` on success that jumps past the un-mark: the answer is right, but the grid stays dirty for whoever calls next. Either funnel the result through a single exit, or repeat the un-mark on the success path. A second leak is the mark placed before a guard that can reject the cell — mark first, discover the cell does not match, return without un-marking. Guard first, mark second. ## Overwriting the cell instead of keeping a visited grid A common variant skips the separate visited grid entirely: overwrite the cell with a sentinel value that cannot appear in the alphabet, recurse, then write the original value back. It saves the auxiliary grid, but it trades away three things. It **mutates the caller's input**, so the grid cannot be shared with anything else reading it during the search; it needs a sentinel guaranteed outside the value domain, which fails the day someone adds a new symbol; and any exit path that forgets the restore corrupts the data itself rather than just a side table. The bookkeeping obligation is identical either way — only the blast radius of forgetting differs. ## Cost Un-marking is O(1) per recursive call, so it changes nothing asymptotically; it is pure correctness. What it does *not* do is bound the work: because marks are cleared, the same cell is entered again by many different partial paths, and the search stays exponential in the target's length rather than linear in the number of cells. Removing the un-mark would make the search cheaper *and* wrong — a trade nobody wants.

  • When is never un-marking the correct design instead of a bug?
    When the mark means "resolved", not "on the current path" — a reachability sweep that only wants the set of cells connected to a start. There, a cell's answer is final the first time it is reached, no other route can improve it, and keeping the mark is what gives the linear, each-cell-once bound. Path search has no such finality: failing on one path says nothing about another.
  • Where exactly in the frame should the un-mark sit, and what commonly slips past it?
    After the loop over neighbours and before returning, on every exit path. The usual leak is an early return on success that jumps over it, leaving the grid dirty for the next query. A second leak is marking before the guards, so a cell rejected by a bounds or value check returns without restoring. Guard first, mark second, un-mark on the single exit.
  • What breaks if you allocate a fresh visited grid for each starting cell instead of un-marking?
    Nothing across queries, but everything inside one. The blocking bug is not between starts, it is between sibling branches of the same start: a branch that fails still leaves its cells marked for the next branch under the same root. A per-start grid also costs an allocation proportional to the whole grid for every one of the rows-times-columns starts.

It is a trail of breadcrumbs you pick back up as you retreat. Leave them lying and the next explorer thinks the whole corridor is already taken.

saying these in an interview costs you the question

  • Reads the mark as "already checked, never revisit"
  • Calls the un-mark an optimization that can be skipped
  • Thinks a fresh visited grid per start removes the need to un-mark
  • Un-marks only on failure, leaving the grid dirty after a match
  • Marks the cell before the guards that can reject it

context

open as a page

Why is grid path backtracking not O(rows x cols) even though visited cells are marked?

level: middleimportance: must knowfreq 62%

basics

~20 s

The mark is scoped to one path, not to the whole search, so it is cleared on the way out and the same cell is re-entered by exponentially many different partial paths. The cost is about rows times cols starting points, each exploring roughly 3 to the power of the target length.

open as a page

In grid backtracking, what goes wrong when the recursive step indexes a cell before checking bounds?

level: middleimportance: should knowfreq 48%

basics

~20 s

Indexing first reads a cell that does not exist. Bounds-checked runtimes raise an error; unchecked ones read whatever memory is adjacent, and a flat row-major grid silently wraps a column overflow into the neighbouring row, producing a wrong answer with no error at all.

open as a page

Searching a grid for many target sequences at once, why prune on prefixes rather than full matches?

level: seniorimportance: should knowfreq 41%

basics

~20 s

A membership test only rules a branch out at full target length, by which point the whole subtree has already been explored. A prefix test rules it out at depth d and discards everything beneath — and the subtree beneath is where all the cost lives.

open as a page