Vertices, edges, adjacency lists — and the two traversals (BFS, DFS) that solve connected components, shortest unweighted paths, cycle detection, and topological sort.
0 of 8 lessons checked off
Introduction
What it is
A graph is a set of vertices connected by edges — the general structure of relationships. Directed or undirected, weighted or unweighted, cyclic or acyclic: four switches that classify every graph problem.
Trees are graphs with n − 1 edges and no cycles; linked lists are path graphs. Graphs are the superset where anything may connect to anything.
Why it matters
Most real systems are graphs: social networks, road maps, dependency builds, currency exchanges, state machines. Interviewers use graphs to test modelling — recognising that a word ladder or a grid of islands IS a graph is half the problem.
Two traversals carry the entire topic: BFS (by distance, via queue) and DFS (by depth, via stack/recursion). Nearly every classic — components, cycles, topological sort, bipartite check — is one of them plus bookkeeping.
How it works
Store adjacency lists: {node: [neighbours]} — O(V + E) space, instant neighbour iteration. (Adjacency matrices cost O(V²) and pay off only for dense graphs or O(1) edge tests.)
BFS: queue + mark-visited-on-enqueue → visits in distance order → shortest paths in unweighted graphs.
Grids are graphs in disguise: cells are vertices, the 4 directions are edges. No adjacency structure needed — generate neighbours on the fly.
Where it's used
Package managers topologically sort dependency graphs; GPS runs shortest-path over road graphs; recommendation engines walk user-item graphs; compilers order build targets.
In interviews
Number of islands, clone graph, course schedule (cycle/toposort), rotting oranges (multi-source BFS), word ladder (implicit graph BFS), pacific-atlantic (multi-source DFS).
Analogy: A city map: intersections are vertices, streets are edges. BFS is the ripple of a stone dropped at your house — reaching everything 1 block away, then 2. DFS is a determined explorer with a ball of string, walking every street to its end before backtracking.
Interactive diagram
The queue enforces distance order: all 1-edge nodes, then 2, then 3. First arrival = shortest path.
BFS from A
Breadth-first search explores in rings: all distance-1 nodes, then distance-2, and so on. A queue (FIFO) enforces that order.
queue
[A]
1 / 8
Lessons in this topic
Check off lessons as you go — your progress is saved in this browser.
Recursive and iterative; pre/post times; backtracking.
30 min
Connected components & flood fill
Loop over vertices, traverse from each unvisited one.
20 min
Cycle detection
Undirected: parent tracking. Directed: the three-colour scheme.
25 min
Bipartite checking
2-colouring by BFS; odd cycles are the only obstacle.
15 min
Topological sorting
Kahn's indegree queue and DFS post-order; DAGs only.
30 min
Operations
Breadth-first search
A queue explores by distance layers. Mark visited when ENQUEUING — the invariant that keeps every vertex in the queue at most once.
BFS from A
Breadth-first search explores in rings: all distance-1 nodes, then distance-2, and so on. A queue (FIFO) enforces that order.
queue
[A]
1 / 8
1fromcollectionsimportdeque234defbfs_distances(graph:dict[str,list[str]],start:str)->dict[str,int]:5"""Fewest-edge distance to every reachable node. O(V + E)."""6dist={start:0}7queue=deque([start])8whilequeue:9node=queue.popleft()10forneighbouringraph[node]:11ifneighbournotindist:# visited check = distance recorded12dist[neighbour]=dist[node]+113queue.append(neighbour)14returndist
Time: O(V + E)Space: O(V)
Edge cases
Disconnected vertices simply never appear in dist — that's the reachability answer too.
Multi-source BFS: seed the queue with ALL sources at distance 0 (rotting oranges).
Path reconstruction: store parent[neighbour] = node and walk back.
Common mistakes
Marking visited at dequeue time — a vertex can be enqueued many times; on dense graphs that's exponential blowup.
Using list.pop(0) as the queue: O(V²).
Depth-first search
Dive along one path to exhaustion, backtrack, repeat. Recursion IS the stack; the iterative version makes it explicit.
DFS from A
Depth-first search dives down one path as far as possible before backtracking. A stack (or recursion) remembers where to resume.
stack
[A]
1 / 8
1defdfs_recursive(graph:dict[str,list[str]],start:str)->list[str]:2"""Visit order from start. O(V + E)."""3visited:set[str]=set()4order:list[str]=[]56defvisit(node:str)->None:7visited.add(node)8order.append(node)9forneighbouringraph[node]:10ifneighbournotinvisited:11visit(neighbour)1213visit(start)14returnorder151617defcount_components(graph:dict[str,list[str]])->int:18"""Connected components in an undirected graph."""19visited:set[str]=set()20components=021fornodeingraph:# restart DFS at every unseen node22ifnodenotinvisited:23components+=124stack=[node]25whilestack:26current=stack.pop()27ifcurrentinvisited:28continue29visited.add(current)30stack.extend(nforningraph[current]ifnnotinvisited)31returncomponents
Time: O(V + E)Space: O(V)
Edge cases
Recursion depth can hit V (path graphs) — Python's limit says convert big-graph DFS to the explicit stack.
The components loop over ALL vertices is what catches disconnected pieces.
Iterative DFS may push a node twice; the visited check at pop time handles it.
Common mistakes
Forgetting the outer loop and reporting only the start's component.
10⁵-node recursive DFS in Python — RecursionError in production, mention the iterative rewrite.
Cycle detection in a directed graph (three colours)
WHITE unvisited, GRAY in the current recursion path, BLACK finished. Meeting a GRAY node means the path loops back into itself — a cycle.
Cycle detection in a directed graph (three colours)
1defhas_cycle_directed(graph:dict[str,list[str]])->bool:2"""Three-colour DFS. O(V + E)."""3WHITE,GRAY,BLACK=0,1,24colour={node:WHITEfornodeingraph}56defdfs(node:str)->bool:7colour[node]=GRAY# entered the current path8forneighbouringraph[node]:9ifcolour[neighbour]==GRAY:# back edge → cycle10returnTrue11ifcolour[neighbour]==WHITEanddfs(neighbour):12returnTrue13colour[node]=BLACK# fully explored, safe forever14returnFalse1516returnany(colour[n]==WHITEanddfs(n)forningraph)
Time: O(V + E)Space: O(V)
Edge cases
A BLACK neighbour is NOT a cycle — it's a finished branch reached again (diamond shapes).
Self-loops: immediately GRAY → cycle.
Undirected graphs use a different test (an edge back to any visited non-parent) — don't mix them.
Common mistakes
Using a single visited set for directed cycles — flags diamonds as cycles (false positives).
Never resetting to BLACK, which makes everything look like a cycle.
Topological sort (Kahn's algorithm)
Repeatedly remove vertices with zero remaining prerequisites. If everything gets removed, that removal order IS a valid schedule; leftovers mean a cycle.
Topological sort (Kahn's algorithm)
1fromcollectionsimportdeque234deftopological_order(graph:dict[str,list[str]])->list[str]|None:5"""Kahn's algorithm. Returns None if the graph has a cycle. O(V + E)."""6indegree={node:0fornodeingraph}7fornodeingraph:8forneighbouringraph[node]:9indegree[neighbour]+=11011queue=deque(nodefornode,dinindegree.items()ifd==0)12order:list[str]=[]13whilequeue:14node=queue.popleft()15order.append(node)16forneighbouringraph[node]:17indegree[neighbour]-=1# 'remove' the edge18ifindegree[neighbour]==0:# prerequisites satisfied19queue.append(neighbour)2021returnorderiflen(order)==len(graph)elseNone
Time: O(V + E)Space: O(V)
Edge cases
len(order) < V ⇔ a cycle exists — Kahn's doubles as cycle detection (Course Schedule).
Multiple valid orders exist; a heap instead of a queue gives the lexicographically smallest.
Only DAGs (directed acyclic) can be topologically sorted — undirected graphs make no sense here.
Common mistakes
Building indegrees from the wrong edge direction — prerequisites arrows must point prerequisite → dependent.
Forgetting the final length check and returning a partial order on cyclic input.
Complexity analysis
Operation
Best
Average
Worst
Space
BFS / DFS traversal
O(V + E)
O(V + E)
O(V + E)
O(V)
Connected components
O(V + E)
O(V + E)
O(V + E)
O(V)
Cycle detection
O(V + E)
O(V + E)
O(V + E)
O(V)
Topological sort
O(V + E)
O(V + E)
O(V + E)
O(V)
Adjacency matrix traversal
O(V²)
O(V²)
O(V²)
O(V²)
O(V + E) is the universal graph-traversal price: touch each vertex once and each edge once (twice undirected). The matrix row shows why sparse graphs use lists.
Python implementation
Production-quality code with type hints, validation, and docstrings.
Number of Islands — grid BFS, the shape of half of all graph interviews
1fromcollectionsimportdeque234defnum_islands(grid:list[list[str]])->int:5"""Count 4-connected regions of '1's. O(rows·cols) time and space."""6ifnotgridornotgrid[0]:7return08rows,cols=len(grid),len(grid[0])9visited:set[tuple[int,int]]=set()10islands=01112defbfs(sr:int,sc:int)->None:13queue=deque([(sr,sc)])14visited.add((sr,sc))15whilequeue:16r,c=queue.popleft()17fordr,dcin((1,0),(-1,0),(0,1),(0,-1)):18nr,nc=r+dr,c+dc
What interviewers expect you to know
What interviewers expect you to know
BFS = queue = shortest unweighted paths; DFS = stack/recursion = components, cycles, orderings. Choosing correctly is the first grading point.
Visited-on-enqueue for BFS; three colours for directed cycles; parent-tracking for undirected cycles.
O(V + E) for everything on this page, and adjacency lists as the default representation.
Grid = implicit graph; word-ladder-style puzzles = implicit graph over states.
Modelling questions to rehearse
"Cells/islands/regions" → grid graph, flood fill components.
"Minimum steps/moves from X to Y" → BFS over states, even when states are words or lock combinations.
"Can the network be split / are these two connected?" → components (or union-find, next topic).
How to talk through a graph problem
Declare the graph first: 'vertices are __, edges are __, directed? weighted?' — before any algorithm talk.
State the traversal invariant: BFS 'queue holds the frontier in distance order'; DFS 'gray nodes are my current path'.
Common mistakes
Forgetting visited entirely
Any cycle loops the traversal forever. Every graph walk carries a visited set — no exceptions, even 'obviously acyclic' inputs.
Visited at dequeue instead of enqueue
BFS-correctness AND performance bug: nodes enter the queue multiple times, distances come out wrong, dense graphs explode.
One visited-set scheme for both directions
Directed cycle detection needs the GRAY/on-path distinction; undirected needs parent exclusion. Swapping them yields false cycles on diamonds or misses real ones.
Only traversing from one start
Components, bipartite checks, and cycle detection need the outer loop over all vertices — disconnected pieces exist.
O(N * L^2) where N is list size and L is word length time · O(N * L) space
Topic quiz
6 questions across concepts, code output, complexity, and scenarios. Answer all, then submit for explanations.
Frequently asked questions
How do I know a problem is secretly a graph problem?
Look for entities with pairwise relationships and a reachability/ordering/grouping question: grids of cells, words one letter apart, courses with prerequisites, accounts sharing emails. Say 'vertices are X, edges are Y' — if that sentence lands, it's a graph.
Adjacency list or matrix in interviews?
List (dict of lists) 95% of the time: O(V+E) space and neighbour iteration match traversal costs. A matrix earns its O(V²) only for dense graphs or repeated O(1) 'does edge (u,v) exist?' checks.
DFS or BFS when both work (e.g. counting components)?
Truly interchangeable for reachability — pick the one you write fastest and say why it doesn't matter here. The forced choices: shortest unweighted path → BFS; anything needing path-order/finish-order (cycles, toposort-by-DFS) → DFS.
Summary & cheat sheet
Key takeaways
Declare vertices, edges, directedness, and weights before choosing any algorithm.
BFS = queue = distance layers = shortest unweighted paths; mark visited on enqueue.