207. Course Schedule
mediumAsked at CitadelCycle detection in a directed graph is a fundamental primitive in dependency resolution — trade execution sequencing, pipeline DAG validation, and margin calculation dependency graphs all require verifying no circular dependencies exist. Citadel expects both DFS (with coloring) and topological-sort (Kahn's) approaches.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Source citations
Public interview reports confirming this problem appears in Citadel loops.
- Glassdoor (2025-12)— Citadel SWE interviewees report graph cycle detection and topological sort as a recurring category in medium-difficulty on-site rounds.
- Blind (2025-10)— Citadel SWE threads identify Course Schedule as a benchmark for graph fundamentals — BFS topo-sort vs DFS coloring is a common follow-up.
Problem
There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai. Return true if you can finish all courses. Otherwise, return false.
Constraints
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesAll the pairs prerequisites[i] are unique.
Examples
Example 1
numCourses = 2, prerequisites = [[1,0]]trueExplanation: Take 0 first, then 1. No cycle.
Example 2
numCourses = 2, prerequisites = [[1,0],[0,1]]falseExplanation: Circular dependency: 0 requires 1 and 1 requires 0.
Approaches
1. DFS with three-color marking
Color nodes: 0 = unvisited, 1 = in current path (gray), 2 = fully processed (black). A back edge (visiting a gray node) indicates a cycle.
- Time
- O(V+E)
- Space
- O(V+E)
function canFinish(numCourses, prerequisites) {
const adj = Array.from({ length: numCourses }, () => []);
for (const [a, b] of prerequisites) adj[b].push(a);
const color = new Array(numCourses).fill(0); // 0=unvisited, 1=visiting, 2=done
function hasCycle(node) {
if (color[node] === 1) return true; // back edge -> cycle
if (color[node] === 2) return false; // already processed, safe
color[node] = 1; // mark visiting
for (const neighbor of adj[node]) {
if (hasCycle(neighbor)) return true;
}
color[node] = 2; // mark done
return false;
}
for (let i = 0; i < numCourses; i++) {
if (hasCycle(i)) return false;
}
return true;
}Tradeoff: O(V+E) time and space. Three-color marking is cleaner than a simple visited/inStack boolean pair because it avoids the need to reset inStack after backtracking. This is the DFS-first answer.
2. Kahn's algorithm (BFS topological sort)
Count in-degrees. Start with all nodes of in-degree 0. Process them via BFS, decrementing neighbors' in-degrees. If all nodes are processed, the graph is acyclic.
- Time
- O(V+E)
- Space
- O(V+E)
function canFinish(numCourses, prerequisites) {
const adj = Array.from({ length: numCourses }, () => []);
const inDegree = new Array(numCourses).fill(0);
for (const [a, b] of prerequisites) {
adj[b].push(a);
inDegree[a]++;
}
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (inDegree[i] === 0) queue.push(i);
}
let processed = 0, qi = 0;
while (qi < queue.length) {
const node = queue[qi++];
processed++;
for (const neighbor of adj[node]) {
if (--inDegree[neighbor] === 0) queue.push(neighbor);
}
}
return processed === numCourses;
}Tradeoff: O(V+E) time, O(V) queue space. Iterative — no recursion stack depth issue. Also produces the topological order as a side effect. Citadel may prefer this because it's easier to reason about in a distributed execution context.
Citadel-specific tips
State both approaches upfront and ask which one the interviewer prefers. DFS with coloring is compact; Kahn's is iterative and naturally parallelizable (zero in-degree nodes can be processed in parallel). In a trading pipeline dependency graph, Kahn's maps to a task scheduler that can batch-execute all tasks with no outstanding dependencies simultaneously — mention this framing. Also: 'The check processed === numCourses at the end tells us whether all nodes were reachable from the zero-in-degree frontier, i.e., no cycle existed.'
Common mistakes
- Using a simple visited boolean instead of three colors in DFS — you can't distinguish back edges (cycle) from cross edges (already processed in another path) with a binary flag alone.
- Forgetting to initialize the adjacency list for all nodes — nodes with no edges still exist and must be visited.
- In Kahn's, not checking processed === numCourses at the end — just checking queue empty is insufficient (queue empties when stuck in a cycle too).
- Building the graph with edge direction reversed — prerequisites[i] = [a, b] means b → a, not a → b. Getting this backward flips the topological order.
Follow-up questions
An interviewer at Citadel may pivot to one of these next:
- Course Schedule II (LC 210) — return the actual topological order, not just a boolean.
- Alien Dictionary (LC 269) — infer topological order from a sorted word list.
- How would you detect cycles in an undirected graph?
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
FAQ
Why three colors instead of two booleans (visited + inStack)?
Three colors are equivalent but express intent more cleanly. Color 2 (black) means 'fully processed — safe to skip.' With two booleans, you need to reset inStack on backtrack, which is easy to forget.
What does Kahn's algorithm fail to do that DFS succeeds at?
Kahn's doesn't naturally identify which nodes form the cycle — it just reports that a cycle exists (processed < numCourses). DFS can be extended to record the cycle path.
Is topological sort uniquely defined?
No — multiple valid topological orderings exist whenever there are nodes with no ordering constraint between them. If you need lexicographically smallest, use a min-heap instead of a plain queue in Kahn's.
Practice these live with InterviewChamp.AI
Drill Course Schedule and other Citadel interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →