CodeRigor

LESSON

Use a stack for depth-first search

Explore a deterministic LIFO branch, prevent duplicate discovery and recognize that DFS paths need not be shortest.

Depth-first search expands the newest scheduled work first. A LIFO stack follows a branch before returning to older frontier work. It can find a route, but does not guarantee a route with the fewest edges.

We use an iterative stack and mark cells when pushed. Push legal neighbors in reverse order so the first direction in our documented order is popped next. This fixes our traversal deterministically. Because sibling cells can already be discovered on the stack, do not claim this parent tree is identical to every recursive DFS implementation.

LIFO follows a branch
start / 3
Newest work is expanded first; older alternatives wait on the stack.
LIFO follows a branch
Newest work is expanded first; older alternatives wait on the stack.

A Python list is suitable as a stack: append and pop use its end. If you push up, right, down and left in that order, left comes off first. Reverse the neighbor list before pushing to make up the first available choice.

Discovery still happens when pushing. A cycle must not repeatedly reinsert known cells. First parents remain fixed even if another branch later encounters the same vertex.

Python
frontier = ["older branch"]
for neighbor in reversed(["up", "right", "down"]):
    frontier.append(neighbor)
print(frontier.pop())
print(frontier)

On the fork fixture, the rightward branch goes around a wall before reaching the goal. The stack follows it even though a two-step route down from the start is waiting. The resulting path is valid and longer. That is evidence of DFS behavior, not a bug in path reconstruction.

The exported trace records the frontier, expansion sequence and discovery order after each expansion. Those are copied lists. If snapshots retained references to the live lists, the ready frame could silently turn into the final frame.

A textbook adjacency-list search runs in O(V+E) time with O(V) discovery storage. This bounded teaching lab additionally validates grid calls and copies growing trace snapshots. Those costs can make the full exported run O(V²) in time and trace storage. The 16 × 16 limit keeps it inspectable; for larger graphs, validate once and stream small events instead.

First discovery owns the parent
start / 3
A discovered set prevents duplicate scheduling. The parent map records the first incoming edge and is never overwritten.
First discovery owns the parent
A discovered set prevents duplicate scheduling. The parent map records the first incoming edge and is never overwritten.
Python
parents = {"start":None}
for current, other in [("start","A"),("start","B"),("B","A")]:
    if other not in parents:
        parents[other] = current
print(parents)

Complete the search and inspect the ready, expansion and done snapshots. A one-cell start/goal should expand once; an isolated start with a different goal should exhaust its frontier. Check every non-root parent is an actual legal neighbor and every expanded cell appears only once.

The next path lesson follows these parent links. It does not infer a route by drawing lines through the expansion sequence.

Python BFS and DFS export discovery, expansion and parent-path evidence for an interactive replay.

Enable JavaScript to step, play/pause and reset the supplied search traces, or download the Python lab and offline viewer.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Optional graph-search companion; all grid code and explanations are included here.