🧩 Data Structures & Algorithms · Chapter 15
Graphs: BFS and DFS
BFS spreads like a ripple; DFS dives like a maze-runner. Same graph, same O(V + E) cost — but only one of them finds the fewest-stops route.
Study this lesson free in the app →
The basics
A graph is a set of nodes (vertices) joined by edges — cities and roads, people and friendships, web pages and links. BFS (breadth-first search) and DFS (depth-first search) are the two basic ways to visit every node you can reach from a starting point.
The problem it solves: "Can I get from here to there?", "What's the fewest number of hops?", "How many separate groups are there?", "Is there a cycle?" — almost every graph question starts with a systematic walk that never visits a node twice.
The mental model: drop a stone in a pond. The ripple reaches everything 1 step away, then 2 steps, then 3 — that's BFS, driven by a queue. Now explore a cave: follow one tunnel as deep as it goes, and only when it dead-ends, back up to the last fork — that's DFS, driven by a stack (or recursion).
Cost: both are O(V + E) time with an adjacency list, and O(V) extra space for the visited set plus the queue or stack.
Words you must know 5
- Adjacency list
- for each node, the list of its neighbours
- Visited set
- nodes already discovered; without it, cycles loop forever
- Unweighted shortest path
- fewest edges; BFS finds it
- Connected component
- a group of nodes all reachable from each other
- V, E
- number of vertices and edges
Explained
Your metro map: which station is the fewest stops away? Your social network: who is a friend of a friend? Your course plan: in what order can you take classes with prerequisites?
Three different questions, one toolkit: walk the graph without visiting anything twice. The only real choice is whether you walk wide (BFS) or deep (DFS).
The ripple and the cave
A rumour (BFS). You tell your news to your 3 closest friends. Each tells their friends who haven't heard yet. Everyone who hears it on day 1 is one hop away, day 2 is two hops. Ask "how many days until Maria hears?" and the answer is exactly her distance in the friendship graph. Nobody hears it twice, because people who already know just say "old news" — that's the visited set.
A cave (DFS). You explore with a ball of string. Take the first tunnel and keep going. At a dead end, wind the string back to the last fork and try its next tunnel. Chalk an X at every junction you've passed so you never loop. You'll see the whole cave, but the order tells you nothing about which chamber is closest to the entrance.
Representing the graph
The graph used in the next two animations:
A ─── B
│ │
C ─── D ─── F ─── G
│ │
E ──────────┘
adjacency list (neighbours in edge order):
A: B, C E: C, F
B: A, D F: D, E, G
C: A, D, E G: F
D: B, C, FAdjacency list — dict of lists. O(V + E) space; iterating a node's neighbours costs its degree. The default choice. Adjacency matrix — V × V grid of 0/1. O(V²) space, O(1) 'is there an edge?'. Only for small or very dense graphs.
With a list, BFS/DFS take each node out once (V) and scan each edge from both ends (2E) → O(V + E). With a matrix, scanning a row is V, so it becomes O(V²).
BFS from A: ripples
Queue starts [A]. Take A, add B and C. Take B, add D. Take C, add E (D is already seen). Take D, add F. Take E (nothing new). Take F, add G. Visit order: A B C D E F G. Distances: B, C = 1; D, E = 2; F = 3; G = 4.
DFS from A: dive, then backtrack
Recursive DFS taking neighbours in the listed order: A → B → D (B's first new neighbour) → C (D's first new neighbour) → E → F → G. Visit order: A B D C E F G. It reached C third, through a 3-edge path, although C is directly connected to A — DFS order says nothing about distance.
BFS distances, DFS two ways
from collections import deque
graph = {"A": ["B", "C"], "B": ["A", "D"], "C": ["A", "D", "E"],
"D": ["B", "C", "F"], "E": ["C", "F"], "F": ["D", "E", "G"],
"G": ["F"]}
def bfs_dist(graph, start):
dist = {start: 0} # doubles as the visited set
q = deque([start])
while q:
node = q.popleft()
for nb in graph[node]:
if nb not in dist:
dist[nb] = dist[node] + 1
q.append(nb)
return dist # A0 B1 C1 D2 E2 F3 G4
def dfs(graph, node, seen=None):
seen = set() if seen is None else seen
seen.add(node)
for nb in graph[node]:
if nb not in seen:
dfs(graph, nb, seen)
return seen
def dfs_iter(graph, start): # no recursion limit
seen, stack, order = set(), [start], []
while stack:
node = stack.pop()
if node in seen:
continue
seen.add(node); order.append(node)
for nb in reversed(graph[node]): # reversed → same order as recursive
if nb not in seen:
stack.append(nb)
return order # A B D C E F G
def count_components(graph):
seen, count = set(), 0
for v in graph:
if v not in seen:
count += 1
seen |= dfs(graph, v)
return countWhich one, and what it costs
BFS DFS
──────────────────────────────────────────────────────────
structure queue (deque) stack / recursion
time O(V + E) O(V + E)
extra space O(V) — widest frontier O(V) — longest path
fewest-hops path yes no
good for levels, nearest X, components, cycles,
shortest unweighted topological sort,
path, 'within k hops' mazes, backtrackingOn grids (mazes, maps, game boards) each cell is a node with up to 4 neighbours: V = rows × cols, E ≈ 2V, so BFS is O(rows × cols).
At real scale: a social graph with 1 billion users and ~200 friends each has ~100 billion edges — full BFS is a distributed job. 'Friends within 2 hops' stays cheap only because you stop after level 2: 200 + 200² ≈ 40,000 nodes.
Gotchas
- No visited set — the first cycle (A → B → D → C → A) loops forever.
- Marking visited on dequeue in BFS — D is reachable from both B and C, so it gets queued twice; on dense graphs the queue explodes. Mark when you enqueue.
list.pop(0)as a queue — O(n) per pop turns BFS into O(V²). Usecollections.deque.- BFS on weighted edges — it minimises edges, not kilometres or minutes. Use Dijkstra.
- Recursive DFS on a 100,000-node path — Python's ~1,000-frame limit. Use an explicit stack.
- Expecting one 'correct' DFS order — a stack pushing neighbours in order visits A C E F G D B; recursion visits A B D C E F G. Both are valid DFS orders.
- Forgetting disconnected parts — one BFS only reaches its own component; loop over all nodes.
Words to know
| Graph | A set of nodes (vertices) connected by edges, which may be directed or undirected, weighted or unweighted. |
|---|---|
| Adjacency list | A map from each node to the list of its neighbours; O(V + E) space. |
| Adjacency matrix | A V × V grid where cell (u, v) marks an edge; O(V²) space, O(1) edge lookup. |
| Breadth-first search | Visiting nodes in order of distance from the start using a queue; finds fewest-edge paths. |
| Depth-first search | Following one path as deep as possible before backtracking, using a stack or recursion. |
| Connected component | A maximal group of nodes in which every node can reach every other. |
| Topological sort | An ordering of a directed acyclic graph's nodes so every edge points forward; built with DFS or Kahn's BFS. |
In two minutes
- A graph is nodes plus edges; store it as an adjacency list — O(V + E) space — unless it's tiny or very dense.
- BFS: queue, explores level by level from the start; the first time it reaches a node is via the fewest edges.
- DFS: stack or recursion, follows one path as deep as possible, then backtracks.
- Both visit each node once and look at each edge a constant number of times: O(V + E) time, O(V) extra space.
- Always keep a visited set, and in BFS mark a node visited when you enqueue it, not when you dequeue it.
- BFS → fewest hops, levels, nearest X. DFS → reachability, connected components, cycle detection, topological order, backtracking.
- BFS minimises edge count, not total weight — weighted shortest paths need Dijkstra (a heap).
Test yourself
1. Which data structure drives breadth-first search?
- A stack
- A queue
- A min-heap
- A hash map on its own
Show the answer
A queue — First in, first out: nodes discovered earlier (closer to the start) are expanded first, so the search moves outward level by level. A stack gives DFS; a min-heap gives Dijkstra.
2. Time complexity of BFS or DFS on a graph stored as an adjacency list?
- O(V²)
- O(V log V)
- O(E log V)
- O(V + E)
Show the answer
O(V + E) — Each vertex is processed once and each edge is examined from its endpoints a constant number of times. O(V²) is what you get with an adjacency matrix; E log V is Dijkstra with a heap.
3. You need the route with the fewest stops between two metro stations (every hop counts the same). Use…
- BFS
- DFS
- Either — both find it
- Neither — you need Dijkstra
Show the answer
BFS — BFS reaches nodes in order of edge count, so the first time it reaches the target is via the fewest hops. DFS can arrive along a long detour first. Dijkstra works too but is unnecessary when all edges weigh the same.
4. A social network has 1 million users with about 100 friends each. How should you store it?
- An adjacency matrix — 10¹² cells
- A single edge list scanned for every query
- An adjacency list — about 100 million neighbour entries
- A sorted array of user names
Show the answer
An adjacency list — about 100 million neighbour entries — ≈50 million friendships, each stored from both sides → ~100 million entries, fine in memory, and neighbour lookups cost only the user's degree. A matrix would need a trillion cells, almost all zero. Scanning an edge list per query is O(E) each time.
5. How do you count the separate friend groups (connected components) in an undirected graph?
- Run one BFS from the first node and count levels
- Sort nodes by degree
- Loop over all nodes; start a BFS/DFS from each one not yet visited; count the starts
- Run Dijkstra from every node
Show the answer
Loop over all nodes; start a BFS/DFS from each one not yet visited; count the starts — Each fresh start floods exactly one component. Total cost is still O(V + E) because the shared visited set means no node is processed twice. A single BFS only sees the component it started in.
6. Courses have prerequisites (a directed graph). Which technique gives a valid order to take them?
- Topological sort — reversed DFS postorder, or Kahn's BFS on nodes with no remaining prerequisites
- Plain BFS from any course
- Binary search over course IDs
- A min-heap ordered by course name
Show the answer
Topological sort — reversed DFS postorder, or Kahn's BFS on nodes with no remaining prerequisites — Topological sort puts every course after all its prerequisites, and detects impossible plans (a cycle). Plain BFS from one course ignores edges coming into later courses and can place a course before a prerequisite.
There are 8 questions, 4 puzzles and 6 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.
Start this lesson →