The Story — Two Ways to Search a Building
You check every room close to the gate first. Then you check the rooms one step further. Then the next ring. You spread out like ripples in water. This is BFS — Breadth-First Search.
Your friend picks one corridor and walks to the very end. Only when there is nowhere left to go, they come back and try the next corridor. This is DFS — Depth-First Search.
Both of you will check every room. You just check them in a different order.
Traversal means visiting every node of a graph (or tree) one time. A graph is a set of nodes (circles) joined by edges (lines). To traverse it, we need two things: a rule for "which node next?" and a way to remember which nodes we have already seen.
BFS and DFS are almost the same code. The only real difference is the waiting line. BFS uses a queue (first in, first out). DFS uses a stack (last in, first out). Change the container, and you change the whole search.
BFS vs DFS at a Glance
Watch Them Race — Animated Diagram
Same tree, same start node 1. Watch the order in which each algorithm lights up the nodes. The small number on each node shows when it was visited.
BFS — level by level
DFS — deep first
BFS order: 1 → 2 → 3 → 4 → 5 → 6 → 7. DFS order: 1 → 2 → 4 → 5 → 3 → 6 → 7.
The Traversal Simulator — Build Your Own Graph
This is your playground. Draw nodes, join them with edges, pick an algorithm, and press Step. Each click runs one line of the algorithm. You will see the queue or stack fill and empty, the current line of code, and a plain-English note of what just happened.
1. Load the Tree. Type your guess for DFS, then check it.
2. Load Graph + Cycle and run BFS. Watch the d= labels. They are the shortest hop counts.
3. Run DFS (stack) and DFS (recursion) on the same graph. The visit order is the same. Only the container changes.
4. Draw two separate groups of nodes. See how the start node's group is visited, but the other group is not.
This simulator uses undirected edges. Neighbours are always checked from the smallest number to the largest. A different order gives a different (but still correct) traversal. In DFS with a stack, we push neighbours largest first, so the smallest one comes out first.
BFS Step by Step — Trace Table
Here is BFS on the sample tree (1 is the root; 2 and 3 are its children; 4, 5 under 2; 6, 7 under 3).
| Step | Dequeue | Add to Queue | Queue After (front → rear) | Visit Order |
|---|---|---|---|---|
| 0 | — | 1 | [1] | — |
| 1 | 1 | 2, 3 | [2, 3] | 1 |
| 2 | 2 | 4, 5 | [3, 4, 5] | 1, 2 |
| 3 | 3 | 6, 7 | [4, 5, 6, 7] | 1, 2, 3 |
| 4 | 4 | — | [5, 6, 7] | 1, 2, 3, 4 |
| 5 | 5 | — | [6, 7] | 1, 2, 3, 4, 5 |
| 6 | 6 | — | [7] | 1, 2, 3, 4, 5, 6 |
| 7 | 7 | — | [ ] | 1, 2, 3, 4, 5, 6, 7 |
DFS Step by Step (Stack)
| Step | Pop | Push (largest first) | Stack After (bottom → top) | Visit Order |
|---|---|---|---|---|
| 0 | — | 1 | [1] | — |
| 1 | 1 | 3, 2 | [3, 2] | 1 |
| 2 | 2 | 5, 4 | [3, 5, 4] | 1, 2 |
| 3 | 4 | — | [3, 5] | 1, 2, 4 |
| 4 | 5 | — | [3] | 1, 2, 4, 5 |
| 5 | 3 | 7, 6 | [7, 6] | 1, 2, 4, 5, 3 |
| 6 | 6 | — | [7] | 1, 2, 4, 5, 3, 6 |
| 7 | 7 | — | [ ] | 1, 2, 4, 5, 3, 6, 7 |
In BFS, mark a node as seen when you add it to the queue, not when you take it out. If you wait, the same node can enter the queue many times. The answer stays right, but the work grows a lot.
Python Implementation
We store the graph as an adjacency list: each node maps to a list of its neighbours.
BFS with a Queue
from collections import deque
graph = {
1: [2, 3],
2: [1, 4, 5],
3: [1, 6, 7],
4: [2], 5: [2], 6: [3], 7: [3],
}
def bfs(graph, start):
visited = {start} # mark when we enqueue
queue = deque([start])
order = []
while queue:
node = queue.popleft() # take from the FRONT
order.append(node)
for n in graph[node]:
if n not in visited:
visited.add(n)
queue.append(n) # add to the REAR
return order
print("BFS:", bfs(graph, 1))
list.pop(0) is slow. It shifts every item, so it costs O(n).
deque.popleft() costs O(1). For big graphs this makes a huge difference.
DFS with a Stack
def dfs_stack(graph, start):
visited = set()
stack = [start]
order = []
while stack:
node = stack.pop() # take from the TOP
if node in visited:
continue
visited.add(node)
order.append(node)
for n in reversed(graph[node]): # so smallest pops first
if n not in visited:
stack.append(n)
return order
print("DFS (stack):", dfs_stack(graph, 1))
DFS with Recursion
def dfs_rec(graph, node, visited=None, order=None):
if visited is None:
visited, order = set(), []
visited.add(node)
order.append(node)
for n in graph[node]:
if n not in visited:
dfs_rec(graph, n, visited, order) # go deeper
return order
print("DFS (recursion):", dfs_rec(graph, 1))
Python stops recursion at about 1,000 calls by default. A long chain of nodes will crash
with RecursionError. For large graphs, use the stack version.
Bonus — BFS Finds the Shortest Path
def shortest_hops(graph, start):
dist = {start: 0}
queue = deque([start])
while queue:
node = queue.popleft()
for n in graph[node]:
if n not in dist:
dist[n] = dist[node] + 1
queue.append(n)
return dist
print(shortest_hops(graph, 1))
Queue vs Stack — Side by Side
| Action | Queue |
|---|---|
| enqueue 2 | [2] |
| enqueue 3 | [2, 3] |
| dequeue | 2 out → [3] |
| Rule | Oldest leaves first |
| Action | Stack |
|---|---|
| push 2 | [2] |
| push 3 | [2, 3] |
| pop | 3 out → [2] |
| Rule | Newest leaves first |
Time and Space Complexity
V = number of nodes (vertices). E = number of edges.
| Property | BFS | DFS |
|---|---|---|
| Time (adjacency list) | O(V + E) | O(V + E) |
| Time (adjacency matrix) | O(V²) | O(V²) |
| Extra space | O(V) — queue can hold a whole level | O(V) — stack can hold a whole path |
| Shortest path (unweighted) | Yes | No |
| Memory on wide graphs | High | Low |
| Memory on deep graphs | Low | High |
When to Use Which
Golden Rules
RecursionError.