2. Course Schedule (Topological Sort + Cycle Detection)
ai-labs-coding.) A Blind thread on DeepMind's research-engineer interview process describes coding round 1 as a LeetCode-medium problem plus a medium/hard follow-up. Course-schedule-style topological sort with cycle detection is a textbook LeetCode-medium graph problem matching that reported difficulty tier; the literal problem used in any specific loop is not publicly known.Problem
A training/eval pipeline is made of stages with hard sequential dependencies — e.g. "tokenize" must run before "train," and "train" must run before "eval." Before submitting a pipeline to the scheduler, validate that its dependency graph is actually runnable: given the number of stages and a list of prerequisite pairs, determine whether all stages can be completed, i.e. whether the dependency graph is a DAG (no cycle).
Stages are numbered 0..num_stages-1. Each prerequisite entry is
(stage, prereq), meaning stage depends on
prereq completing first.
Source: src/2_course_schedule_cycle_detection.py
def can_complete_all_stages(num_stages: int, prerequisites: list[tuple[int, int]]) -> bool: ...
>>> can_complete_all_stages(4, [(1, 0), (2, 1), (3, 2)])
True
>>> can_complete_all_stages(2, [(0, 1), (1, 0)])
False
Step-by-Step Approach
- Build an adjacency list from the prerequisite pairs: for each
(stage, prereq), add an edgeprereq -> stage(prereq unlocks stage). - Compute each stage's indegree — the number of unfinished prerequisites still
blocking it. Stages that start with indegree
0have no prerequisites and can run immediately. - Seed a queue with every indegree-
0stage. This is Kahn's algorithm: repeatedly "complete" a stage from the queue, then decrement the indegree of every stage it unlocks. - Whenever decrementing a downstream stage's indegree brings it to
0, all of its prerequisites are now satisfied — push it onto the queue too. - Count how many stages get processed this way in total.
- If every stage was eventually processed, the graph is a DAG (no cycle) and
can_complete_all_stagesreturnsTrue. If some stages never reach indegree0— because they're stuck in a cycle where each is waiting on another that's waiting on it — they're left unprocessed, and the count comes up short, so the function returnsFalse.
The key insight is that a cycle is exactly the set of nodes that Kahn's algorithm can never "unlock," because every node in a cycle depends on another node in the same cycle, so none of them can ever reach indegree zero on their own — comparing the processed count to the total node count is a cheap, direct way to detect that without any separate DFS-based cycle check.
Reference solution
from collections import deque
def can_complete_all_stages(num_stages: int, prerequisites: list[tuple[int, int]]) -> bool:
# Kahn's algorithm: repeatedly peel off indegree-0 nodes; if every node
# eventually gets peeled, the graph is acyclic. O(V + E) time/space.
graph: dict[int, list[int]] = {stage: [] for stage in range(num_stages)}
indegree = [0] * num_stages
for stage, prereq in prerequisites:
graph[prereq].append(stage)
indegree[stage] += 1
queue = deque(stage for stage in range(num_stages) if indegree[stage] == 0)
processed = 0
while queue:
node = queue.popleft()
processed += 1
for nxt in graph[node]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
return processed == num_stages # leftover nodes mean a cycle held them back
Key Functions & Tricks
collections.deque—O(1)pops from the front, unlike a plain list.- Indegree array — tracks how many unmet prerequisites remain per stage, updated incrementally rather than recomputed.
- Kahn's algorithm (BFS-based topological sort) — repeatedly consumes indegree-0
nodes; naturally produces a valid run order as a side effect (not used here, but
processed == num_stagesis the acyclicity check derived from it). processed == num_stages— the whole cycle-detection logic reduces to this one comparison; no separate visited/recursion-stack DFS is needed.- Dict comprehension for the adjacency list — initializes every stage's edge list up front, even ones with no outgoing edges.
How to Recognize This Pattern
Signal words: "can all tasks/courses/stages be completed," "is this dependency graph
valid (no cycle)," "find a valid execution order given prerequisites." The tell is a
directed graph of dependencies where you need to check runnability or produce a
valid order — that's topological sort territory (Kahn's BFS-based algorithm or DFS
with a recursion-stack cycle check). Common variations: actually returning the valid
order itself (LC210) instead of just a boolean, which needs almost no extra code —
just collect the popped nodes into a list; or detecting a cycle via DFS with three
colors (unvisited/in-progress/done) instead of Kahn's algorithm, which is equally
valid and sometimes preferred when an explicit cycle path needs to be reported. A
common pitfall is getting the edge direction backwards — it's easy to accidentally
build stage -> prereq instead of prereq -> stage,
which silently inverts the whole computation.