Lesson 164 min read

Graphs: BFS, DFS, Topological Sort, Union-Find

November 05, 2026

// TL;DR

Many 'grid' and 'network' problems are graphs in disguise (a grid cell connects to its 4 neighbours). Build an ADJACENCY LIST, then pick the tool: BFS (queue) for shortest paths in an UNWEIGHTED graph and level structure; DFS (recursion/stack) for reachability, connected components, islands and cycles; TOPOLOGICAL SORT (Kahn's algorithm using in-degrees, or DFS) to order tasks with dependencies — also detects cycles; UNION-FIND (disjoint set) to answer 'are these two connected?', count components, and detect cycles as you add edges, near O(1) each. Signals: 'connected', 'shortest path', 'course schedule/ordering', 'number of islands', 'redundant connection'. For weighted shortest paths, use Dijkstra (a heap-based BFS).

Graphs look intimidating, but interview graph problems reduce to four tools on top of one structure (an adjacency list). Learn when to use each and most graph questions become routine — including grid problems, which are graphs in disguise.

The idea

Graphs, BFS, DFS, topological sort, union-find. A small graph with nodes 0 to 4 and several edges. The four tools: BFS with a queue for shortest path in an unweighted graph, level by level; DFS with stack or recursion for reachability, connected components, cycles; topological sort to order tasks with dependencies like course schedule; union-find to answer are two nodes connected, detect cycles, count components. The idea: most grid and network problems are graphs in disguise, a grid cell connects to its 4 neighbours; build an adjacency list then pick the tool, BFS for shortest unweighted paths and level structure, DFS for components islands and cycles, topological sort when there are dependencies to order, and union-find to answer are these connected and detect cycles as you add edges, near O(1) each. Use it for number of islands, clone graph, course schedule topo, word ladder BFS, redundant connection union-find, Dijkstra.

Represent the graph as an adjacency list ({node: [neighbours]}). Then choose:

  • BFS (queue) — shortest path in an unweighted graph, level structure.
  • DFS (recursion/stack) — reachability, connected components, islands, cycle detection.
  • Topological sort — ordering tasks with dependencies (and detecting cycles).
  • Union-Find (disjoint set) — ”are these connected?”, counting components, detecting cycles as edges are added.

A huge class of problems are grids — treat each cell as a node connected to its up-to-4 neighbours.

DFS: number of islands (grid as graph)

Flood-fill each unvisited land cell, marking it visited; count how many flood-fills you start:

def num_islands(grid):
    if not grid: return 0
    R, C = len(grid), len(grid[0]); count = 0
    def dfs(r, c):
        if r < 0 or c < 0 or r >= R or c >= C or grid[r][c] != '1':
            return
        grid[r][c] = '0'                       # mark visited
        dfs(r+1,c); dfs(r-1,c); dfs(r,c+1); dfs(r,c-1)
    for r in range(R):
        for c in range(C):
            if grid[r][c] == '1':
                count += 1; dfs(r, c)
    return count

Topological sort: course schedule

Can you finish all courses given prerequisites? Compute each node’s in-degree, start from the zero-in-degree nodes, and repeatedly remove them (Kahn’s algorithm). If you process all nodes, there’s no cycle:

from collections import defaultdict, deque
def can_finish(n, prereqs):
    adj = defaultdict(list); indeg = [0]*n
    for a, b in prereqs:            # b -> a (b is a prerequisite of a)
        adj[b].append(a); indeg[a] += 1
    q = deque([i for i in range(n) if indeg[i] == 0])
    seen = 0
    while q:
        u = q.popleft(); seen += 1
        for v in adj[u]:
            indeg[v] -= 1
            if indeg[v] == 0: q.append(v)
    return seen == n               # all processed → no cycle
# can_finish(2, [[1,0]]) -> True ;  can_finish(2, [[1,0],[0,1]]) -> False

Union-Find: detect a redundant connection

Union-Find answers connectivity and detects the edge that creates a cycle, near O(1) per operation with path compression:

def find_redundant(edges):
    parent = list(range(len(edges) + 1))
    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]      # path compression
            x = parent[x]
        return x
    for a, b in edges:
        ra, rb = find(a), find(b)
        if ra == rb:                           # already connected → this edge makes a cycle
            return [a, b]
        parent[ra] = rb                        # union
    return []
# find_redundant([[1,2],[1,3],[2,3]]) -> [2,3]

Step-by-step walkthrough

Trace Course Schedule II (topological sort, Kahn’s algorithm) with 4 courses and prerequisites 0→1, 0→2, 1→3, 2→3. Start with every course that has no prerequisites; taking a course lowers its dependents’ in-degree, and any that hit zero become available.

take in-degrees after newly free queue order so far
0 [0, 0, 0, 2] [1, 2] [1, 2] [0]
1 [0, 0, 0, 1] — [2] [0, 1]
2 [0, 0, 0, 0] [3] [3] [0, 1, 2]
3 [0, 0, 0, 0] — [] [0, 1, 2, 3]

Order: [0, 1, 2, 3]. All 4 courses were processed, so there is no cycle. If a cycle existed, some in-degree would never reach zero and the order would come up short — that’s the cycle check.

When to reach for which

  • “Shortest path / fewest steps” (unweighted) → BFS (word ladder, shortest path in a grid).
  • “Explore / count regions / detect a cycle” → DFS (islands, clone graph, flood fill).
  • “Order tasks with dependencies” / “is it possible given prerequisites” → topological sort (course schedule, alien dictionary).
  • “Are these connected? / number of groups / redundant edge” → union-find.
  • “Shortest path with weights” → Dijkstra (a heap-based BFS, Lesson 14’s heap + graph).

Pitfalls

  • Not marking visited. Infinite loops come from re-visiting nodes. Mark on enqueue/visit, not on dequeue, for BFS to avoid duplicates.
  • DFS recursion depth on big grids. A huge grid can blow the stack; mention an explicit stack or BFS if asked.
  • Topo sort on a cyclic graph. If not all nodes are processed, there’s a cycle — that is often the answer (can’t finish).

Recap

Build an adjacency list, then pick the tool: BFS for shortest unweighted paths, DFS for components/cycles/islands, topological sort (in-degrees, Kahn’s) for dependency ordering, union-find for connectivity. Grids are graphs (cells = nodes). For weighted shortest paths, Dijkstra (heap + BFS). Signal words: connected, shortest, schedule/order, islands, redundant edge.

Practice: 12 solved problems on graphs → — cover each one, solve it yourself, then check the tested code.

Next: Lesson 17 — Dynamic Programming I (1-D).

exercisePractice this patternGraphs — Practice Problemsarrow_forward