BFS vs DFS: Which Traversal Should You Use?
In this article we will learn how breadth-first search and depth-first search actually differ, why BFS finds the shortest path in an unweighted graph and DFS does not, how their memory usage compares on wide and deep graphs, and a decision rule for choosing between them in an interview.
BFS and DFS are the two ways to visit every node of a graph, and almost every graph algorithm is one of them with something extra bolted on. Choosing correctly between them is usually the first decision in a graph problem, and getting it wrong makes the rest of the solution much harder than it needs to be.
In this article we will learn how the two traversals actually differ, why BFS finds the shortest path in an unweighted graph and DFS does not, how their memory usage compares on wide versus deep graphs, and a decision rule you can apply in an interview.
What is breadth-first search?
Short answer
Breadth-first search visits nodes in order of their distance from the starting node: all nodes one edge away, then all nodes two edges away, and so on. It uses a queue. Because it explores in expanding rings, the first time it reaches a node is guaranteed to be by a shortest path in an unweighted graph.
What is depth-first search?
Short answer
Depth-first search follows one path as far as it goes before backtracking to try the next branch. It uses a stack, usually the call stack via recursion. It reaches nodes quickly but with no guarantee about path length — the first time it reaches a node may be by a long, winding route.
How do they differ in practice?
Consider this graph, with A as the start:
BFS visits: A, then B and C, then D, E and F, then G. Level by level.
DFS visits: A, B, D, G, backtrack, E, backtrack, C, F. One branch to the bottom, then the next.
Both reach G. BFS reaches it at distance 3 via A → B → D → G, and that is genuinely the shortest. DFS also happens to reach it via that route here, but only by luck of the ordering — reorder the adjacency lists and DFS could find a longer path first and accept it.
The implementations
from collections import deque
def bfs(graph, start):
visited = {start}
queue = deque([start])
order = []
while queue:
node = queue.popleft() # FIFO — this is what makes it breadth-first
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
visited.add(neighbour) # mark on enqueue, not on dequeue
queue.append(neighbour)
return orderdef dfs(graph, start, visited=None, order=None):
if visited is None:
visited, order = set(), []
visited.add(start)
order.append(start)
for neighbour in graph[start]:
if neighbour not in visited:
dfs(graph, neighbour, visited, order)
return orderThe only structural difference is the queue versus the stack. Swap popleft() for pop() in the BFS code and you have an iterative DFS.
Mark nodes visited when you enqueue them in BFS, not when you dequeue them. Marking on dequeue lets the same node be added to the queue several times before it is first processed, which can blow up memory on dense graphs.
Which one should you use?
| BFS | DFS | |
|---|---|---|
| Data structure | Queue (FIFO) | Stack / recursion (LIFO) |
| Finds shortest path (unweighted) | Yes, guaranteed | No |
| Time complexity | O(V + E) | O(V + E) |
| Space complexity | O(w) — widest level | O(d) — deepest path |
| Good on deep, narrow graphs | Yes | Risk of stack overflow |
| Good on wide, shallow graphs | Queue can get large | Yes |
| Cycle detection | Works | More natural |
| Topological sort | Kahn's algorithm | Natural via post-order |
| Connected components | Works | Works, usually simpler |
| Finds a path fast | Slower | Often faster |
The decision rule
Ask one question: does the problem care about distance?
- "Shortest", "minimum number of steps", "fewest moves", "level", "nearest" → BFS
- "Does a path exist", "all paths", "connected components", "detect a cycle", "topological order" → DFS
Two practical caveats. On a graph with weighted edges, neither is correct for shortest paths — you need Dijkstra. And if the graph might be very deep, recursive DFS can exhaust the call stack; convert it to an explicit stack.
Common mistakes
- Using DFS for a shortest-path question. The most common graph error in interviews.
- Marking visited at the wrong moment in BFS. See the callout above.
- Forgetting the visited set entirely. On a cyclic graph, both traversals loop forever.
- Recursing on a deep graph. Python defaults to a recursion limit around 1,000.
- Assuming BFS works for weighted graphs. It gives fewest edges, not lowest cost.
Conclusion
BFS and DFS have identical O(V + E) time complexity and differ only in the order they visit nodes: BFS in expanding rings from the source using a queue, DFS one branch at a time using a stack.
That ordering difference is decisive. BFS guarantees shortest paths in unweighted graphs and uses memory proportional to the widest level. DFS gives no path-length guarantee, uses memory proportional to the deepest path, and is the more natural fit for cycle detection, topological sorting and exhaustive path enumeration.
If distance matters, use BFS. Otherwise DFS is usually simpler.