◐ ai-teacher

🧩 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, F

Adjacency 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 count

Which 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, backtracking

On 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

Words to know

GraphA set of nodes (vertices) connected by edges, which may be directed or undirected, weighted or unweighted.
Adjacency listA map from each node to the list of its neighbours; O(V + E) space.
Adjacency matrixA V × V grid where cell (u, v) marks an edge; O(V²) space, O(1) edge lookup.
Breadth-first searchVisiting nodes in order of distance from the start using a queue; finds fewest-edge paths.
Depth-first searchFollowing one path as deep as possible before backtracking, using a stack or recursion.
Connected componentA maximal group of nodes in which every node can reach every other.
Topological sortAn ordering of a directed acyclic graph's nodes so every edge points forward; built with DFS or Kahn's BFS.

In two minutes

Test yourself

1. Which data structure drives breadth-first search?

  1. A stack
  2. A queue
  3. A min-heap
  4. 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?

  1. O(V²)
  2. O(V log V)
  3. O(E log V)
  4. 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…

  1. BFS
  2. DFS
  3. Either — both find it
  4. 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?

  1. An adjacency matrix — 10¹² cells
  2. A single edge list scanned for every query
  3. An adjacency list — about 100 million neighbour entries
  4. 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?

  1. Run one BFS from the first node and count levels
  2. Sort nodes by degree
  3. Loop over all nodes; start a BFS/DFS from each one not yet visited; count the starts
  4. 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?

  1. Topological sort — reversed DFS postorder, or Kahn's BFS on nodes with no remaining prerequisites
  2. Plain BFS from any course
  3. Binary search over course IDs
  4. 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.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →