Given numCourses and a list of prerequisite pairs, decide whether you can finish all courses — which is true exactly when the dependency graph contains no cycle. Kahn's BFS topological sort detects cycles in one pass by counting how many nodes it can fully process.
You have numCourses courses labelled 0 to numCourses - 1. Each pair [a, b] means “you must take b before a.” Can you complete all courses?
Example: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]. Taking 0, then 1 and 2 (order either way), then 3 works → true.
Cycle example: numCourses = 2, prerequisites = [[1,0],[0,1]]. You need 0 before 1 and 1 before 0 — impossible → false.
[a, b], add edge b → a (b unlocks a) and increment indegree[a].indegree === 0 have no prerequisites — they can be taken right now.finished, and for each neighbor decrement its indegree. If that neighbor's indegree hits 0, enqueue it.finished === numCourses → no cycle, true; otherwise a cycle trapped some courses.[a, b]means “b before a” — the directed edge goes from b to a, not a → b. Reversing this gives wrong indegrees and a broken sort.Both approaches are O(V + E) time and O(V + E)space. Kahn's is usually easier to implement iteratively (no recursion depth concern) and also gives you the actual topological order — useful in Course Schedule II.
0 has no prerequisites — enqueue them first.1function canFinish(numCourses: number, prerequisites: number[][]): boolean {2 // Build adjacency list and indegree counter3▶ const adj: number[][] = Array.from({ length: numCourses }, () => []);4▶ const indegree: number[] = new Array(numCourses).fill(0);56▶ for (const [a, b] of prerequisites) {7▶ adj[b].push(a); // b must be taken before a => edge b -> a8▶ indegree[a]++;9 }1011 // Seed the queue with all courses that have no prerequisites12 const queue: number[] = [];13 for (let i = 0; i < numCourses; i++) {14 if (indegree[i] === 0) queue.push(i);15 }1617 let finished = 0;18 while (queue.length > 0) {19 const cur = queue.shift()!;20 finished++;21 for (const neighbor of adj[cur]) {22 indegree[neighbor]--;23 if (indegree[neighbor] === 0) queue.push(neighbor);24 }25 }2627 // If every course was processed there is no cycle28 return finished === numCourses;29}
function canFinish(numCourses: number, prerequisites: number[][]): boolean {
// Build adjacency list and indegree counter
const adj: number[][] = Array.from({ length: numCourses }, () => []);
const indegree: number[] = new Array(numCourses).fill(0);
for (const [a, b] of prerequisites) {
adj[b].push(a); // b must be taken before a => edge b -> a
indegree[a]++;
}
// Seed the queue with all courses that have no prerequisites
const queue: number[] = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i);
}
let finished = 0;
while (queue.length > 0) {
const cur = queue.shift()!;
finished++;
for (const neighbor of adj[cur]) {
indegree[neighbor]--;
if (indegree[neighbor] === 0) queue.push(neighbor);
}
}
// If every course was processed there is no cycle
return finished === numCourses;
}adj[b].push(a) records that finishing b unlocks a; indegree[a]++ counts how many prerequisites a still has. Edge direction is b → a because b must come first.indegree === 0have no outstanding prerequisites. They are the “roots” of the DAG and can be taken immediately.indegree[neighbor]simulates “this prerequisite is now satisfied.” When indegree reaches 0, the course is newly unlocked and joins the queue.finished < numCourses.O(V + E) time: every node is enqueued and dequeued at most once; every edge is visited exactly once when we walk adj[cur]. Space is also O(V + E) for the adjacency list and O(V) for indegrees and the queue.result.length === numCourses, else [].numCourses = 1 with no prerequisites is trivially true. An empty prerequisites list means all courses are independent and trivially orderable.function canFinish(numCourses: number, prerequisites: number[][]): boolean {
// Build adjacency list and indegree counter
const adj: number[][] = Array.from({ length: numCourses }, () => []);
const indegree: number[] = new Array(numCourses).fill(0);
for (const [a, b] of prerequisites) {
adj[b].push(a); // b must be taken before a => edge b -> a
indegree[a]++;
}
// Seed the queue with all courses that have no prerequisites
const queue: number[] = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i);
}
let finished = 0;
while (queue.length > 0) {
const cur = queue.shift()!;
finished++;
for (const neighbor of adj[cur]) {
indegree[neighbor]--;
if (indegree[neighbor] === 0) queue.push(neighbor);
}
}
// If every course was processed there is no cycle
return finished === numCourses;
}O(V + E) time: every node is enqueued and dequeued at most once; every edge is visited exactly once when we walk adj[cur]. Space is also O(V + E) for the adjacency list and O(V) for indegrees and the queue.A schedule is feasible iff the prerequisite graph has no cycle. Naively launch a fresh DFS from each course, carrying a per-start visiting set, and report failure if any walk revisits a node already on the current path.
function canFinish(numCourses: number, prerequisites: number[][]): boolean {
const adj: number[][] = Array.from({ length: numCourses }, () => []);
for (const [a, b] of prerequisites) adj[b].push(a); // b -> a
// Does a cycle exist on a path starting from 'node'?
function hasCycle(node: number, visiting: Set<number>): boolean {
if (visiting.has(node)) return true; // back-edge on current path
visiting.add(node);
for (const next of adj[node]) {
if (hasCycle(next, visiting)) return true;
}
visiting.delete(node);
return false;
}
for (let i = 0; i < numCourses; i++) {
if (hasCycle(i, new Set<number>())) return false; // fresh state each start
}
return true;
}O(V·(V + E)). Adding a global “done” set, or using Kahn's indegree BFS (the optimal), visits each node and edge once for O(V + E).| "can all tasks/courses be completed" with dependencies | Kahn's BFS topological sort + cycle check |
| directed dependency graph, detect cycle | BFS indegree drain OR 3-color DFS |
| "return a valid ordering" of tasks with prerequisites | Kahn's BFS — collect dequeued order |
| "no cycle" / "acyclic" / "DAG" in disguise | finished === numNodes check after topo-sort |
const adj: number[][] = Array.from({ length: n }, () => []);
const indegree: number[] = new Array(n).fill(0);
for (const [a, b] of prerequisites) {
adj[b].push(a);
indegree[a]++;
}
const queue: number[] = [];
for (let i = 0; i < n; i++) if (indegree[i] === 0) queue.push(i);
let finished = 0;
while (queue.length > 0) {
const cur = queue.shift()!;
finished++;
for (const nb of adj[cur])
if (--indegree[nb] === 0) queue.push(nb);
}
return finished === n;finished < numCourses after BFS, some nodes were never reachable with indegree 0 — they are stuck in a cycle.V nodes and E edges?prerequisites = [[1,0],[0,1]] with numCourses = 2, what happens during Kahn's algorithm?numCourses = 4 and prerequisites [[1,0],[2,0],[3,1],[3,2]], what is the earliest course that can be dequeued?numCourses = 5, the finished counter equals 3. What does this mean?