Back to Graphs
Q3Question 3 of 23

Course Schedule: cycle detection (can all courses be finished?)

Key Concepts
  • All courses are finishable exactly when the prerequisite directed graph has no cycle
  • DFS with three-color marking (white, gray, black) detects back edges that signal cycles
  • Kahn's algorithm detects cycles implicitly: if the processed count is less than total, a cycle remains
  • The gray-vs-black distinction is essential; a plain visited flag cannot separate back edges from cross edges
  • Time is O(V + E) for both approaches; space is O(V + E) for the adjacency list
  • This reduces to detecting a cycle in a directed graph, a fundamental graph theory problem

Definition

All courses are finishable exactly when the prerequisite directed graph is acyclic; detect a cycle with DFS path-marking or with Kahn's in-degree processing.
  • Course Schedule reduces to detecting a cycle in a directed graph.
  • Adjacency list maps each prerequisite to the courses that depend on it.
  • DFS three-color marks nodes white (unvisited), gray (on current path), black (fully explored); a gray neighbor means a back edge and thus a cycle.
  • Kahn's algorithm processes zero-in-degree nodes; if the processed count falls short of total, a cycle blocks the remaining nodes.
  • Time is O(V + E) for both approaches; space is O(V + E) for the graph.

Think of onboarding steps at the National Bank of India where some steps require others first. You can finish everything only if there is no circular requirement, like step A needing B while B needs A. You either walk each chain watching for a loop back onto the current chain, or you repeatedly complete every step with no outstanding requirements and check that nothing is left stuck in a loop.

Understanding the Concepts

What is the Course Schedule problem?

Given a number of courses and a list of prerequisite pairs [course, prerequisite], determine whether it is possible to finish all courses. This is equivalent to checking whether the prerequisite directed graph is acyclic. If there is a cycle, some courses can never be completed because they depend on each other in a loop.

JAVA
package com.bank.service;

import java.util.*;

public class OnboardingValidator {
    // Kahn's algorithm: can all steps be completed?
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adj = new ArrayList<>();
        int[] inDegree = new int[numCourses];
        for (int i = 0; i < numCourses; i++) adj.add(new ArrayList<>());

        for (int[] pre : prerequisites) {
            adj.get(pre[1]).add(pre[0]);
            inDegree[pre[0]]++;
        }

        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) queue.offer(i);
        }

        int processed = 0;
        while (!queue.isEmpty()) {
            int course = queue.poll();
            processed++;
            for (int next : adj.get(course)) {
                if (--inDegree[next] == 0) queue.offer(next);
            }
        }
        return processed == numCourses;
    }
}

Why does this reduce to cycle detection?

A directed graph is acyclic if and only if it has a valid topological ordering. If a cycle exists, no topological ordering exists because the cycle's nodes all depend on each other. Detecting a cycle is equivalent to checking whether a topological sort is possible.

DIAGRAM
No cycle (finishable):       Cycle (not finishable):
  0 → 1 → 2                   0 → 1
                                ↑   ↓
                                3 ← 2

How does the DFS three-color approach work?

Color each node white (unvisited), gray (on the current recursion path), or black (fully explored). When entering a node, mark it gray. If you encounter a neighbor that is already gray, you have found a back edge and thus a cycle. When leaving a node (all descendants explored), mark it black.

JAVA
public boolean canFinishDFS(int numCourses, int[][] prerequisites) {
    List<List<Integer>> adj = new ArrayList<>();
    for (int i = 0; i < numCourses; i++) adj.add(new ArrayList<>());
    for (int[] pre : prerequisites) adj.get(pre[1]).add(pre[0]);

    int[] color = new int[numCourses];  // 0=white, 1=gray, 2=black
    for (int i = 0; i < numCourses; i++) {
        if (color[i] == 0 && hasCycle(i, adj, color)) return false;
    }
    return true;
}

private boolean hasCycle(int node, List<List<Integer>> adj, int[] color) {
    color[node] = 1;  // gray: on current path
    for (int next : adj.get(node)) {
        if (color[next] == 1) return true;   // back edge → cycle
        if (color[next] == 0 && hasCycle(next, adj, color)) return true;
    }
    color[node] = 2;  // black: fully explored
    return false;
}

Why is three-color marking necessary for DFS?

A plain boolean visited flag cannot distinguish between a back edge (which signals a cycle) and a cross or forward edge to an already-finished node (which does not). The gray state means "I am currently on this path," and the black state means "I already finished exploring this node and found no cycle from it." Only a gray neighbor indicates a cycle.

JAVA
// WRONG: plain visited flag
if (visited[next]) return true;  // may be a cross edge, not a cycle!

// CORRECT: three-color check
if (color[next] == 1) return true;  // gray = back edge = cycle
if (color[next] == 2) continue;     // black = already done, no cycle

How does Kahn's algorithm detect cycles?

Kahn's algorithm processes nodes in topological order by repeatedly removing nodes with zero in-degree. If some nodes can never reach zero in-degree (because they are in a cycle), they are never processed. The cycle is detected implicitly: if the processed count is less than the total number of nodes, a cycle remains.

How do the two approaches compare?

| Aspect | DFS Three-Color | Kahn's Algorithm |
|--------|----------------|------------------|
| Detection method | Explicit back edge | Implicit leftover count |
| Cycle identification | Can find the exact cycle | Only knows a cycle exists |
| Produces topo order | Yes (reverse finish order) | Yes (processing order) |
| Implementation | Recursive | Iterative (queue) |
| Stack overflow risk | Yes on deep graphs | No |

What are edge cases?

  • Zero courses: return true (nothing to do).
  • No prerequisites: all courses have in-degree 0, all are finishable.
  • Self-loop [0, 0]: course 0 requires itself, which is a cycle.
  • Disconnected components: each component must be independently acyclic.
JAVA
// Edge case: self-loop
// prerequisites = [[0, 0]]
// In-degree of 0 is 1, it never reaches 0 → processed count = 0
// 0 < 1 → return false (cycle detected)

What are common mistakes?

The first mistake is using a plain visited boolean instead of three colors in DFS. The second mistake is building edges in the wrong direction, which inverts the prerequisite relationship. The third mistake is forgetting disconnected components and only starting DFS from node 0.

JAVA
// WRONG: edges in wrong direction
adj.get(pre[0]).add(pre[1]);  // inverted!

// CORRECT: edge from prerequisite to dependent
adj.get(pre[1]).add(pre[0]);  // prereq → course

What should a senior engineer know?

At the National Bank of India, a customer-onboarding workflow had steps with prerequisite dependencies. Rajesh validated the workflow with Kahn's algorithm. When the processed count fell short, he knew a misconfiguration had introduced a circular dependency, so the validator rejected the workflow instead of letting it loop forever. The three-color DFS variant was used for debugging to identify the exact cycle and report it to the configuration team.

Visual Overview

DIAGRAM
  Course Schedule: cycle detection
  =================================

  Prerequisite graph (no cycle):
  0 → 1 → 3
  0 → 2

  Kahn's processing:
  ┌──────────────────────────────────┐
  │ In-degrees: [0:0, 1:1, 2:1, 3:1]│
  │ Queue: [0] (zero in-degree)      │
  │ Process 0 → decrement 1,2       │
  │ Queue: [1, 2]                    │
  │ Process 1 → decrement 3         │
  │ Process 2 → (no dependents)     │
  │ Queue: [3]                       │
  │ Process 3                        │
  │ Processed: 4 == total → acyclic  │
  └──────────────────────────────────┘

  DFS three-color:
  ┌─────────────────────────────────┐
  │ Visit 0 (gray)                  │
  │   Visit 1 (gray)                │
  │     Visit 3 (gray → black)      │
  │   1 → black                     │
  │   Visit 2 (gray → black)        │
  │ 0 → black                       │
  │ No gray neighbor found → no cycle│
  └─────────────────────────────────┘

Key Takeaway

Course Schedule is cycle detection in disguise: all courses are finishable exactly when the prerequisite directed graph has no cycle. You can detect the cycle with either DFS three-color marking or Kahn's in-degree algorithm, both running in O(V + E) time.

The DFS approach colors nodes white, gray, and black. Gray means on the current recursion path. Encountering a gray neighbor is a back edge and proves a cycle exists. The distinction between gray and black is essential because a plain visited flag cannot separate back edges from harmless cross edges to already-finished nodes.

Kahn's algorithm detects cycles implicitly. It processes nodes with zero in-degree, decrementing dependents' in-degrees as it goes. If the processed count falls short of the total, some nodes are stuck in a cycle and can never reach zero in-degree.

At the National Bank of India, Rajesh validated customer-onboarding workflows with Kahn's algorithm. When the processed count fell short, a misconfiguration had introduced a circular dependency. The validator rejected it instead of letting the workflow loop forever, and the three-color DFS variant identified the exact cycle for the configuration team.

Remember one sentence: a directed graph is acyclic when every node can be processed, detected by Kahn's leftover count or DFS's three-color back-edge check.

---

Share
Use ← → arrow keys to navigate