Depth-First Search (DFS)
The impetuous search — dive deep, backtrack late. Linear space but no shortest-path guarantee
The Simplest Explanation
DFS is the impetuous tourist. It sees a street and immediately walks down it, as far as it can go. Only when it hits a dead end does it back up one step and try the next street.
The entire algorithm is the general generate-and-test loop with one line deciding its personality:
Add new nodes at the head of Open → Open behaves like a stack (LIFO) → always explore the newest node first.
DFS = add to head = LIFO = go deep first. Everything else about DFS follows from this single choice.
The Algorithm
Open starts as [(S, nil)], Closed as []. Repeat while Open is not empty:
- Pick the first pair
(n, parent)from Open - If GoalTest(n) passes → trace parents and return the path
- Move n to Closed
neighbors = RemoveSeen(MoveGen(n), Closed)newPairs = MakePairs(neighbors, n)- Prepend newPairs to Open (
newPairs ++ Open)
Step 6 is the only difference from BFS.
In this snapshot DFS has visited S → A → D. The frontier nodes (E, B, C) wait on Open while DFS keeps diving. It returns to them only after the deep branch is exhausted.
Interactive Trace
Watch DFS run on a graph where the goal hides behind two routes — a long one through A-C and a short one through D:
Initialize: Open = [(S, nil)], Closed = []. S is the start state.
Key Observations
- DFS dives: from S it commits to A, then C, never glancing at D until forced.
- It finds a longer path: S → A → C → G (length 3), even though S → D → G (length 2) exists. DFS has no sense of direction.
- Open stays thin: at every moment Open holds just a handful of nodes — one or two per level of depth.
Analysis
Assume constant branching factor and goal at depth .
Time Complexity
| Scenario | Nodes inspected |
|---|---|
| Best case (goal on leftmost path) | |
| Worst case (goal on rightmost path) | |
| Average |
On average DFS inspects about half the tree's depth before hitting the goal — if you're lucky enough for the goal to be on an early-explored branch.
Space Complexity — DFS's Superpower
At each level, DFS generates children, picks one, and parks the other on Open. Over depth :
BFS by contrast holds nodes on Open. For large problems, BFS runs out of memory long before time becomes the issue; DFS never does.
Solution Quality and Completeness
| Property | Value |
|---|---|
| Shortest path guaranteed? | No — may find a longer path down an early branch |
| Complete on finite spaces? | Yes — will eventually explore everything |
| Complete on infinite spaces? | No — can dive forever down one infinite branch |
| Optimal? | No |
The Cycle Danger
Without pruning, DFS can loop forever: S → A → S → A → ... Open never empties. This is why loop checking matters more for DFS than for any other algorithm:
- With Closed list: safe, each node expanded once
- Without any pruning: infinite loops on cyclic spaces
Never use plain DFS when (a) the space has cycles and you have no memory for a Closed list, or (b) you need the shortest path. Use DFID instead — it fixes both problems for only ~11% extra work.