Detect Cycle in Directed Graph (DFS)
Does a directed graph contain a cycle? Use DFS with three colours.
Open on GeeksforGeeks ↗Intuition
The undirected parent trick breaks here: reaching an already-visited vertex is fine if that vertex is completely finished — it just means two paths converge. What matters is whether the target is still on the current recursion stack. Three colours capture that: white (untouched), grey (on the stack), black (finished). An edge into grey is a cycle.
Direction changes everything: a revisited vertex is only a cycle if it's still on the current path. Three colours capture that — white unvisited, grey on the recursion stack, black finished. Seeing a grey vertex means you've looped back into your own path; black is a finished sibling branch and harmless.
Approach
Why two states are not enough
With only visited/unvisited, the DAG 0→1, 0→2, 1→2 falsely reports a cycle when 0→2 finds 2 already visited. The fix is distinguishing 'finished' from 'in progress'.
Grey means on the stack
Colour a vertex grey on entry and black on exit. An edge into a grey vertex points back into the path currently being explored — a genuine cycle.
Black edges are safe
An edge into a black vertex is a shortcut into an already-completed subtree; it cannot close a loop, so it is ignored.
Solution & live demo
Common pitfalls
Using a single visited flag
if seen[u]: return True
if color[u] == GREY: return True if color[u] == WHITE and dfs(u): return True
In a DAG like a→b, a→c, b→c, vertex c is legitimately reached twice and a boolean flag calls that a cycle. Only a vertex still on the current path — grey — indicates a genuine back edge.
Never setting vertices black
def dfs(v):
color[v] = GREY
...
return False ...
color[v] = BLACK
return FalseA vertex that stays grey after its branch finishes looks like it's still on the path, so a later unrelated branch reaching it reports a false cycle. Repainting on the way out is what marks the frame as popped.
Reusing the undirected parent trick
elif u != parent: return True
if color[u] == GREY: return True
Directed edges are one-way, so there's no mirrored edge to excuse and the parent test is meaningless — worse, a genuine 2-cycle a→b, b→a would be excused as the parent. Colour, not parentage, is the right signal here.
Edge cases
The shared vertex is black by then, so no false positive.
v is grey while its own edges are scanned — reported immediately.
Outer loop starts a DFS in each component.