DAY 24 / 30 · WEEK 4

Graphs II — Shortest Path & Topo Sort

Goal of the day

Concept

Shortest path in an unweighted graph. Day 23's bfs already does almost all of the work here — it just never told you how far away each node was. BFS explores level by level: it fully processes every node at distance 1 from the start before it ever looks at a node at distance 2. That ordering guarantee means the very first time BFS discovers a node, it has found that node by the shortest possible path (fewest edges). So the only new idea is to track a distance map alongside the visited set: distance[start] = 0, and every time a neighbor is discovered for the first time, it gets distance[current] + 1. Same queue, same visited-on-discovery rule, same traversal — one extra assignment per newly discovered node.

Topological sort. Picture a semester's prerequisite chart: Intro CS unlocks both Discrete Math and Data Structures; Discrete Math and Data Structures both feed into Algorithms; Data Structures also unlocks Databases; and Algorithms and Databases both feed into Systems Design. A topological sort answers "what order can I safely take these courses in?" — an ordering where every prerequisite appears before the course that depends on it. The exact same question shows up as "in what order do I compile these files given their #include dependencies?" or "in what order do I run these build steps?". Kahn's algorithm answers it with a BFS-shaped idea: compute every node's in-degree (how many prerequisites it still has), start a queue with every node whose in-degree is already 0 (no prerequisites), then repeatedly dequeue a node, append it to the result, and decrement the in-degree of everything it points to — enqueuing any neighbor that just dropped to 0.

Two things only make sense together here, and both are worth saying out loud in an interview: topological sort requires a directed graph (an undirected edge has no "prerequisite comes first" direction to exploit), and it only exists if that directed graph has no cycle. If Data Structures secretly required Systems Design and Systems Design required Data Structures, there would be no valid place to start — every course in that cycle is stuck waiting on another course in the same cycle. Kahn's algorithm doesn't need a separate check for this: if the graph has a cycle, some nodes never reach in-degree 0, so the final result comes up short of the total node count — a cycle announces itself by absence rather than by a crash or an infinite loop.

One deliberate scope note: today's shortest-path technique only works because every edge "costs" the same — one hop. If edges have different weights (e.g. road distances, flight prices), BFS's level-by-level order no longer lines up with the lowest-total-weight path, and you'd reach for Dijkstra's algorithm (BFS with a priority queue instead of a plain queue) instead. That's a topic for another day — the unweighted case below is what most interview "shortest path" questions on a plain graph actually want.

Diagram: BFS distances radiating from a start node

Same graph as Day 23 — BFS from A, now labeling each node's distance A B C D E F G d=0 d=1 d=1 d=2 d=3 d=4 d=3 B and C are A's direct neighbors, so both get distance 1. D is 2 hops away, reachable only through B or C. E and G are 3 hops out through D, and F is the farthest at 4 hops.

Diagram: a DAG with a valid topological order

Course prerequisites as a DAG — every edge points from prereq to dependent A B C D E F Intro CS Discrete Math Data Structures Algorithms Databases Systems Design Valid order: A → B → C → D → E → F — every arrow points from an earlier course to a later one. A → C → B → D → E → F is valid too — a DAG can have more than one correct topological order.

Diagram: why a cycle makes topological sort impossible

A directed 3-cycle: X needs Y, Y needs Z, Z needs X X Y Z Every node is stuck waiting on a node that's waiting on it — no node ever has in-degree 0. Kahn's algorithm's queue starts (and stays) empty — order.length is 0, never 3. That gap (0 processed nodes out of 3 total) is exactly how Kahn's algorithm reports "no valid order."

Interactive visualization: BFS shortest path (distance tracking)

in the queue (distance recorded, waiting) dequeued, checking neighbors now fully processed — distance finalized
Press Play or Step to begin.

Interactive visualization: Kahn's algorithm (topological sort)

in-degree just hit 0 — enqueued, waiting dequeued now — decrementing its neighbors' in-degree added to the topological order
Press Play or Step to begin.

The code: BFS shortest path and Kahn's topological sort

// Shortest path (fewest edges) in an UNWEIGHTED graph — Day 23's bfs()
// with one new line: a distance map built up as each node is discovered.
function bfsShortestPath(graph, start) {
  const distance = { [start]: 0 };   // start is 0 hops from itself
  const queue = [start];

  while (queue.length > 0) {
    const node = queue.shift();
    for (const neighbor of graph[node]) {
      if (!(neighbor in distance)) {
        distance[neighbor] = distance[node] + 1;  // one hop further than `node`
        queue.push(neighbor);
      }
    }
  }
  return distance; // { nodeId: distanceFromStart }
}

// Topological sort of a DIRECTED ACYCLIC graph — Kahn's algorithm.
// graph[node] = array of nodes that DEPEND ON node (node must come first).
function topoSortKahn(graph, nodes) {
  const inDegree = {};
  nodes.forEach((n) => { inDegree[n] = 0; });
  for (const n of nodes) {
    for (const neighbor of graph[n]) {
      inDegree[neighbor] = (inDegree[neighbor] || 0) + 1;
    }
  }

  const queue = nodes.filter((n) => inDegree[n] === 0); // no prerequisites
  const order = [];

  while (queue.length > 0) {
    const node = queue.shift();
    order.push(node);
    for (const neighbor of graph[node]) {
      inDegree[neighbor]--;
      if (inDegree[neighbor] === 0) queue.push(neighbor);
    }
  }

  const hasCycle = order.length !== nodes.length;
  return { order, hasCycle };
}

Line by line:

Dry run: BFS shortest path from A (same graph as Day 23)

Queue (before)DequeueNeighbor checksDistance map (after)
[A]A (dist 0)B new → dist 1, enqueue; C new → dist 1, enqueue{A:0, B:1, C:1}
[B, C]B (dist 1)A has a distance, skip; D new → dist 2, enqueue{A:0, B:1, C:1, D:2}
[C, D]C (dist 1)A has a distance, skip; D has a distance, skip{A:0, B:1, C:1, D:2}
[D]D (dist 2)B, C have distances, skip; E new → dist 3, enqueue; G new → dist 3, enqueue{A:0, B:1, C:1, D:2, E:3, G:3}
[E, G]E (dist 3)D has a distance, skip; F new → dist 4, enqueue{..., F:4}
[G, F]G (dist 3)D has a distance, skip{..., F:4}
[F]F (dist 4)E has a distance, skip{A:0, B:1, C:1, D:2, E:3, F:4, G:3}

Final distances from A: A=0, B=1, C=1, D=2, E=3, G=3, F=4 — the same BFS order as Day 23 (A → B → C → D → E → G → F), just now with a number attached to each node the first time it's discovered.

Dry run: Kahn's algorithm on the course-prerequisite DAG

Queue (before)Dequeue → orderDecrement neighbors' in-degreeNewly 0 → enqueueQueue (after)
[A]AB: 1→0, C: 1→0B, C[B, C]
[B, C]BD: 2→1(none, still waiting on C)[C]
[C]CD: 1→0, E: 1→0D, E[D, E]
[D, E]DF: 2→1(none, still waiting on E)[E]
[E]EF: 1→0F[F]
[F]F(no neighbors — nothing depends on F)(none)[]

Final order: A → B → C → D → E → F — all 6 of 6 nodes processed, so hasCycle is false. This matches the diagram above exactly.

Complexity

OperationTimeSpaceWhy
BFS shortest path (distance tracking)O(V + E)O(V)Exactly Day 23's plain BFS — every vertex is enqueued and processed once, every edge is examined once per endpoint. Recording a distance is O(1) extra work per newly discovered node, so it doesn't change the bound.
Topological sort (Kahn's algorithm)O(V + E)O(V)Computing every node's in-degree scans all edges once (O(E)). Each node enters and leaves the queue exactly once (O(V)), and each edge is examined exactly once when decrementing a neighbor's in-degree (O(E)) — no node or edge is revisited.
Cycle detection (side effect of Kahn's)O(1) extraO(1) extraJust compare order.length to the total node count after the algorithm finishes — no separate traversal is needed to detect the cycle.

Interview corner

How to talk through it out loud: for shortest path, say the connection first — "this is the same BFS as Day 23, I'm just adding a distance map that increments by one each time I discover a new node" — so the interviewer hears you reusing a known-correct traversal rather than inventing something new. For topological sort, name the two preconditions before writing a line of code — "this only works because the graph is directed and, I'm assuming, acyclic — I'll use Kahn's algorithm with in-degree counting, and if the result comes up short of the total node count, that tells me there's a cycle and no valid order exists." Interviewers on both of these are listening for whether you state the underlying guarantee (BFS's level-order; the DAG requirement) rather than just producing code that happens to work on the one example in front of you.

Practice problems

Note: this set is intentionally Easy/Medium/ Medium rather than Easy/Medium/Hard — there's no clean, distinct Easy-tier problem that exercises real topological-sort mechanics beyond in-degree counting, so problem 1 uses the in-degree idea alone (no queue/ordering) as the easy on-ramp, and problems 2-3 cover the two core techniques from today at Medium difficulty each.

1. Find the Town Judge (Easy — LeetCode: Find the Town Judge)

In a town of n people, the judge trusts nobody but is trusted by everyone else. Given a list of [a, b] "a trusts b" pairs, find the judge, or return -1 if none exists.

2. Course Schedule (Medium — LeetCode: Course Schedule)

Given numCourses and a list of [course, prerequisite] pairs, return whether it's possible to finish all courses. The direct, unmodified application of today's Kahn's algorithm.

3. 01 Matrix (Medium — LeetCode: 01 Matrix)

Given a binary matrix, return a matrix where each cell holds the distance to its nearest 0-cell. Today's distance-tracking BFS, generalized to start from every 0-cell at once instead of a single start node — multi-source BFS.

Quiz

1. Why does plain BFS find the shortest path (fewest edges) in an unweighted graph?

2. When would BFS's shortest-path approach give the WRONG answer, and what would you use instead?

3. In Kahn's algorithm, what does it mean when order.length ends up less than the total number of nodes?

4. In Kahn's algorithm, which nodes go into the queue first?

5. What is the time complexity of Kahn's algorithm, and why?