Given numCourses and a list of prerequisites, return a valid course order — or [] if a cycle makes it impossible. Kahn's BFS drains the graph by repeatedly processing nodes with zero incoming edges, which naturally produces a topological order and detects cycles in one pass.
You have numCourses courses labeled 0 to numCourses - 1. Each pair [a, b] in prerequisites means you must take course b before course a. Return any valid ordering to take all courses, or [] if no valid ordering exists (i.e., there is a cycle).
Worked example: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]. Course 0 has no prerequisites, so start there. That unlocks 1 and 2. After both of those, 3 is unlocked. Answer: [0, 1, 2, 3] (or [0, 2, 1, 3]).
Cycle example: numCourses = 2, prerequisites = [[0,1],[1,0]] → return [].
[]. The count order.length === numCourses is the cycle detector.adj and an indegree[] array, both of size numCourses. For each [course, prereq], add an edge prereq → course and increment indegree[course]. This tracks how many unsatisfied prerequisites each course has.indegree is 0 — these courses have no prerequisites and can be taken immediately.order, then decrement the indegree of each neighbor. If a neighbor's indegree hits zero, enqueue it. This simulates "completing" the course and unlocking dependent courses.order.length === numCourses every course was reachable — no cycle. Otherwise, some courses are stuck in a cycle and were never dequeued. Return [].[a, b]means "take b before a", so the directed edge goes b → a (prereq points to dependent). Many learners accidentally build the reversed graph a → b and get nonsense orderings without an obvious error.Space is O(V + E) for the adjacency list and indegree array. The queue holds at most Vnodes. Both DFS and BFS are optimal; prefer Kahn's when you also need to detect a cycle (the order.length check) without separate bookkeeping.
order.length === n), Alien Dictionary (build graph from character ordering), Minimum Height Trees (repeatedly peel leaf nodes), and Parallel Courses (BFS level = semester number).[0].1function findOrder(numCourses: number, prerequisites: number[][]): number[] {2▶ const adj: number[][] = Array.from({ length: numCourses }, () => []);3▶ const indegree: number[] = new Array(numCourses).fill(0);45▶ for (const [course, prereq] of prerequisites) {6▶ adj[prereq].push(course); // prereq → course7▶ indegree[course]++;8 }910▶ const queue: number[] = [];11▶ for (let i = 0; i < numCourses; i++) {12▶ if (indegree[i] === 0) queue.push(i); // seed: no prerequisites13 }1415 const order: number[] = [];16 while (queue.length > 0) {17 const node = queue.shift()!;18 order.push(node);19 for (const neighbor of adj[node]) {20 indegree[neighbor]--;21 if (indegree[neighbor] === 0) queue.push(neighbor);22 }23 }2425 return order.length === numCourses ? order : [];26}
function findOrder(numCourses: number, prerequisites: number[][]): number[] {
const adj: number[][] = Array.from({ length: numCourses }, () => []);
const indegree: number[] = new Array(numCourses).fill(0);
for (const [course, prereq] of prerequisites) {
adj[prereq].push(course); // prereq → course
indegree[course]++;
}
const queue: number[] = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i); // seed: no prerequisites
}
const order: number[] = [];
while (queue.length > 0) {
const node = queue.shift()!;
order.push(node);
for (const neighbor of adj[node]) {
indegree[neighbor]--;
if (indegree[neighbor] === 0) queue.push(neighbor);
}
}
return order.length === numCourses ? order : [];
}adj[prereq].push(course) records the directed edge from prerequisite to dependent course. indegree[course]++ counts how many unsatisfied prerequisites each course has. Both structures drive the BFS.indegree[i] === 0has no prerequisites — it can be taken immediately. This is the "starting set" for the BFS.order(it's "completed"), then decrement every dependent course's indegree. When a dependent's indegree reaches zero, all its prerequisites are done — enqueue it. Each node is processed exactly once and each edge is visited exactly once, giving O(V + E).order.length === numCourses every course was dequeued — the graph is acyclic and orderis a valid topological order. Otherwise, some courses were never dequeued because their indegrees never reached zero (they're in a cycle). Return [].V nodes is enqueued and dequeued once; each of the E edges is traversed once to decrement indegrees. O(V + E) space for the adjacency list; O(V) for the queue and order arrays.order.length === numCourses instead of order.earliest[neighbor] = Math.max(earliest[neighbor], earliest[node] + weight) during BFS — this is the "critical path" in project scheduling.function findOrder(numCourses: number, prerequisites: number[][]): number[] {
const adj: number[][] = Array.from({ length: numCourses }, () => []);
const indegree: number[] = new Array(numCourses).fill(0);
for (const [course, prereq] of prerequisites) {
adj[prereq].push(course); // prereq → course
indegree[course]++;
}
const queue: number[] = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i); // seed: no prerequisites
}
const order: number[] = [];
while (queue.length > 0) {
const node = queue.shift()!;
order.push(node);
for (const neighbor of adj[node]) {
indegree[neighbor]--;
if (indegree[neighbor] === 0) queue.push(neighbor);
}
}
return order.length === numCourses ? order : [];
}V nodes is enqueued and dequeued once; each of the E edges is traversed once to decrement indegrees. O(V + E) space for the adjacency list; O(V) for the queue and order arrays.Topological sort the slow way: each round, scan all courses for one whose prerequisites are all already taken, append it to the order, and mark it done — repeat until nothing changes. If a full scan adds nobody but courses remain, there's a cycle.
function findOrder(numCourses: number, prerequisites: number[][]): number[] {
const prereqs: number[][] = Array.from({ length: numCourses }, () => []);
for (const [course, prereq] of prerequisites) prereqs[course].push(prereq);
const taken: boolean[] = new Array(numCourses).fill(false);
const order: number[] = [];
for (let round = 0; round < numCourses; round++) {
let progressed = false;
for (let c = 0; c < numCourses; c++) { // full scan every round
if (taken[c]) continue;
if (prereqs[c].every((p) => taken[p])) { // all prereqs satisfied
taken[c] = true;
order.push(c);
progressed = true;
}
}
if (!progressed) break; // stuck → remaining courses form a cycle
}
return order.length === numCourses ? order : [];
}V costs up to O(V² + V·E). Kahn's algorithm (the optimal) tracks indegrees and only re-examines a course's neighbors when its indegree drops, sorting in O(V + E).| "must take X before Y" / dependency ordering | Kahn's BFS topological sort |
| detect a cycle in a directed graph | check order.length vs numCourses |
| "how many rounds / semesters" (parallel processing) | BFS level-by-level |
| "alien dictionary / reconstruct order from constraints" | build graph → topo sort |
const adj: number[][] = Array.from({ length: n }, () => []);
const indegree: number[] = new Array(n).fill(0);
for (const [course, prereq] of prerequisites) {
adj[prereq].push(course);
indegree[course]++;
}
const queue: number[] = [];
for (let i = 0; i < n; i++) if (indegree[i] === 0) queue.push(i);
const order: number[] = [];
while (queue.length > 0) {
const node = queue.shift()!;
order.push(node);
for (const nb of adj[node]) {
if (--indegree[nb] === 0) queue.push(nb);
}
}
return order.length === n ? order : [];numCourses = 4 and prerequisites = [[1,0],[2,0],[3,1],[3,2]], which of the following is a valid output?prerequisites = [[a, b]], which directed edge is added to the adjacency list?numCourses = 2, prerequisites = [[0,1],[1,0]], how many nodes end up in order?