Skip to main content

207. Course Schedule

mediumAsked at Atlassian

Course Schedule is an Atlassian-favorite graph problem because it mirrors Jira's dependency model. Given numCourses and a list of prerequisite pairs, determine whether you can finish all courses — equivalent to detecting a cycle in a directed graph.

By Alex Chen, Founder, InterviewChamp.AI · Last verified

Source citations

Public interview reports confirming this problem appears in Atlassian loops.

  • Glassdoor (2026-Q1)Atlassian SWE-II / Senior onsite reports cite Course Schedule and its 'return order' variant as a recurring graph problem.
  • Blind (2025-09)Atlassian interview threads repeatedly describe Course Schedule as 'the Jira blocker-graph question'.

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] = [a, b] indicates you must take course b first if you want to take course a. Return true if you can finish all courses; otherwise return false.

Constraints

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= a, b < numCourses
  • All the pairs prerequisites[i] are unique.

Examples

Example 1

Input
numCourses = 2, prerequisites = [[1,0]]
Output
true

Explanation: Take 0, then 1.

Example 2

Input
numCourses = 2, prerequisites = [[1,0],[0,1]]
Output
false

Explanation: Cycle: 0 needs 1 needs 0.

Approaches

1. DFS with three-color cycle detection

Color each node white/gray/black. Start a DFS from each unvisited node; finding a gray node mid-DFS = cycle. Returns false on first cycle.

Time
O(V + E)
Space
O(V + E)
function canFinish(numCourses, prerequisites) {
  const graph = Array.from({ length: numCourses }, () => []);
  for (const [a, b] of prerequisites) graph[b].push(a);
  const WHITE = 0, GRAY = 1, BLACK = 2;
  const color = new Array(numCourses).fill(WHITE);
  const dfs = (node) => {
    if (color[node] === GRAY) return false;
    if (color[node] === BLACK) return true;
    color[node] = GRAY;
    for (const next of graph[node]) {
      if (!dfs(next)) return false;
    }
    color[node] = BLACK;
    return true;
  };
  for (let i = 0; i < numCourses; i++) {
    if (!dfs(i)) return false;
  }
  return true;
}

Tradeoff: Cycle detection is the natural framing. Recursion can stack-overflow at 2000 deep on a degenerate chain — name this aloud. Three-color is more defensible than 'visited + in-stack' to interviewers who want textbook precision.

2. Kahn's algorithm (BFS topological sort)

Compute in-degree of each node; enqueue zero-in-degree nodes; pop one, decrement neighbors, enqueue any that hit zero. Cycle iff fewer than numCourses nodes are popped.

Time
O(V + E)
Space
O(V + E)
function canFinishKahn(numCourses, prerequisites) {
  const graph = Array.from({ length: numCourses }, () => []);
  const indeg = new Array(numCourses).fill(0);
  for (const [a, b] of prerequisites) {
    graph[b].push(a);
    indeg[a]++;
  }
  const queue = [];
  for (let i = 0; i < numCourses; i++) {
    if (indeg[i] === 0) queue.push(i);
  }
  let popped = 0;
  while (queue.length) {
    const node = queue.shift();
    popped++;
    for (const next of graph[node]) {
      if (--indeg[next] === 0) queue.push(next);
    }
  }
  return popped === numCourses;
}

Tradeoff: Iterative, no stack-overflow risk, and naturally extends to 'return the order' (LeetCode 210). Atlassian's follow-up almost always asks for the order, so this is the version to lead with at Senior+. queue.shift() is O(n); for the LeetCode constraints it's fine but mention the manual-head-pointer optimization.

Atlassian-specific tips

Atlassian interviewers almost always follow up with 'now return the order you'd take the courses in' (LeetCode 210) or 'now find which course is in the cycle'. Lead with Kahn's algorithm at Senior+ because it extends to both follow-ups without rewriting. At SWE-I / SWE-II the DFS three-color version is equally accepted on the first pass, but you should still mention Kahn's by name to score on the 'knows the toolkit' axis.

Common mistakes

  • Building the graph in the wrong direction — prerequisites[i] = [a, b] means b → a (b must come first), and reversing it silently breaks the test cases.
  • Using a single 'visited' boolean instead of three colors — that misses cycles that revisit a node already finished by a sibling DFS branch.
  • Forgetting the queue.shift() vs queue.pop() distinction — both work for cycle detection but only shift gives a stable topological order.

Follow-up questions

An interviewer at Atlassian may pivot to one of these next:

  • Course Schedule II (LeetCode 210) — return the actual topological order, not just a boolean.
  • Alien Dictionary (LeetCode 269) — build the prerequisite graph from a sorted dictionary first, then apply Kahn's.
  • What if the graph is too big to fit in memory? Stream edges into a disk-based in-degree counter; Kahn's still works since you can re-scan edges in passes.

Solve it now

Free. No sign-up. Python and JavaScript run instantly in your browser.

Output

Press Run or Cmd+Enter to execute

FAQ

DFS or Kahn's at Atlassian?

Either passes correctness, but Kahn's scores higher because it extends cleanly to the 'return the order' follow-up that comes ~70% of the time per Atlassian onsite reports. Pick DFS only if you can articulate why iterative-BFS would be a different conversation.

How does this map to Jira?

Jira issue blockers form exactly this graph: issue A is blocked by issue B means B must be resolved first. Detecting impossible-to-resolve issue cycles is the same algorithm. Atlassian interviewers will probe whether you see the connection.

Free learning resources

Curated free links for this problem.

Practice these live with InterviewChamp.AI

Drill Course Schedule and other Atlassian interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.

Practice these live with InterviewChamp.AI →