GeeksforGeeks Medium

Detect Cycle in Directed Graph (DFS)

Detect Cycle in a Directed Graph is a GFG problem (Medium). You are given a directed graph with V vertices numbered 0 … V-1 and a list edges, where edges[i] = [u, v] is an edge from u to v. Return true if the graph contains at least one cycle, and false otherwise.

  • A cycle is a path that follows edge directions and returns to the vertex it started from.
  • The graph may be disconnected, so a cycle can hide in any part of it.
  • A self-loop u → u is a cycle.

With up to 10⁵ vertices and edges, the check has to be linear, O(V + E).

Constraints
  • 1 <= V <= 10⁵
  • 0 <= E <= 10⁵
graphdfscyclecoloring
Open on GeeksforGeeks ↗
02

Intuition

In a directed graph, a cycle exists exactly when a depth-first search finds a back edge: an edge from the current vertex to a vertex that is still on the current DFS path (an ancestor in the recursion). Following that edge leads back to where the path already is, so the path closes into a loop.

A plain visited set cannot tell a back edge apart from a harmless edge into a part of the graph that was already fully explored. So each vertex gets one of three states:

  • 0, unvisited: not reached yet;
  • 1, on the path: DFS has entered it and not yet left (it is on the recursion stack);
  • 2, finished: every vertex reachable from it has been explored, and no cycle was found through it.

An edge to a state-1 vertex is a cycle. An edge to a state-2 vertex is safe and can be skipped: everything behind it is already known to be cycle-free.

How to spot this pattern

Any task that asks you to find a cycle in a directed graph, or just to say whether one exists, fits here. It appears as course prerequisites (Course Schedule, LeetCode 207), build or task dependencies, deadlock detection and "can these be ordered?" questions. DFS with three states answers it; Kahn's algorithm (BFS on in-degrees) is the other standard method and also produces a topological order.

03

Approach

Try it first

Before reading on: run DFS with only a visited set on the second example (0→1, 0→2, 1→3, 2→3). What happens when the search reaches 3 a second time from 2? What extra information would tell you that 3 is not an ancestor of 2?

1

Build the adjacency list

For each [u, v], append v to adj[u]. Only one direction: the graph is directed. A directed edge u → v can be walked only from u.

2

Mark a vertex as on the path when DFS enters it

Set state[u] = 1 before exploring its edges. A plain visited flag could not tell an edge back into the current path from an edge into an already finished part of the graph, which is why three states are needed.

3

Classify each outgoing edge

  • state[v] == 1: back edge, return true.
  • state[v] == 0: recurse into v; if that finds a cycle, return true.
  • state[v] == 2: already finished and cycle-free, skip it.

Only an edge to a vertex still on the current path can close a loop.

4

Mark the vertex finished when DFS leaves it

After all its edges, set state[u] = 2. It is no longer on the path, so later edges into it are not cycles.

5

Start from every unvisited vertex

Launch DFS from each vertex still in state 0. If none finds a back edge, the graph is acyclic. The graph may be disconnected, and a cycle can sit in a part the first DFS never reaches.

04

Detect Cycle in Directed Graph (DFS) solution in Python | C++ | Java

▶1class Solution:
▶2 def isCyclic(self, V: int, edges: List[List[int]]) -> bool:
▶3 adj = [[] for _ in range(V)]
▶4 for u, v in edges:
▶5 adj[u].append(v)
▶6 # 0 = unvisited, 1 = on the current path, 2 = finished
▶7 state = [0] * V
▶8 
▶9 def dfs(u):
▶10 state[u] = 1
▶11 for v in adj[u]:
▶12 if state[v] == 1:
▶13 return True
▶14 if state[v] == 0 and dfs(v):
▶15 return True
▶16 state[u] = 2
▶17 return False
▶18 
▶19 return any(state[u] == 0 and dfs(u) for u in range(V))
on path (1)finished (2)01234recursion stackemptystate0001020304all vertices unvisited (0)
state[0, 0, 0, 0, 0]0 unvisited, 1 on path, 2 finished
Idea. DFS keeps the vertices of the current path on the recursion stack. If an edge leads to one of them, following it returns to the path: that is a cycle. An edge to a finished vertex is harmless, so the search needs three states, not a single visited flag.
on path (1)finished (2)01234recursion stackdfs(0)state1001020304enter 0 → state 1
u0now on the path
stack[0]the current DFS path
DFS enters 0 and marks it on the path. Until the call for 0 returns, reaching 0 again along an edge would mean the path has looped back on itself.
on path (1)finished (2)01234recursion stackdfs(0)state10010203040 → 1: unvisited → recurse
edge0 → 1state[1] = 0
1 has not been visited, so DFS follows the edge and explores it before trying the rest of 0's edges.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)state1011020304enter 1 → state 1
u1now on the path
stack[0, 1]the current DFS path
DFS enters 1 and marks it on the path. Until the call for 1 returns, reaching 1 again along an edge would mean the path has looped back on itself.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)state10110203041 → 2: unvisited → recurse
edge1 → 2state[2] = 0
2 has not been visited, so DFS follows the edge and explores it before trying the rest of 1's edges.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)dfs(2)state1011120304enter 2 → state 1
u2now on the path
stack[0, 1, 2]the current DFS path
DFS enters 2 and marks it on the path. Until the call for 2 returns, reaching 2 again along an edge would mean the path has looped back on itself.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)dfs(2)state10111203042 → 3: unvisited → recurse
edge2 → 3state[3] = 0
3 has not been visited, so DFS follows the edge and explores it before trying the rest of 2's edges.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)dfs(2)dfs(3)state1011121304enter 3 → state 1
u3now on the path
stack[0, 1, 2, 3]the current DFS path
DFS enters 3 and marks it on the path. Until the call for 3 returns, reaching 3 again along an edge would mean the path has looped back on itself.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)dfs(2)dfs(3)state10111213043 → 1: 1 on path → cycle
edge3 → 11 is on the path
cycle1 → 2 → 3 → 1return true
Cycle found. 1 is still on the recursion stack, so the edge 3 → 1 goes back to an ancestor. The path 1 → 2 → 3 plus this edge is a loop. Return true all the way up; nothing else needs exploring.
on path (1)finished (2)01234recursion stackdfs(0)dfs(1)dfs(2)dfs(3)state1011121304back edge found → return true
resulttruecycle 1 → 2 → 3 → 1
Cyclic. The red edges form the loop. Vertices and edges DFS never reached did not need to be explored: one back edge settles the answer.
05

Common pitfalls

Using one visited set, as for undirected graphs

✗ Wrong
if v in visited:
    return True
✓ Right
if state[v] == 1:  # on the current path
    return True

In the second example DFS reaches 3 through 1, then again through 2. With only visited, the second arrival looks like a cycle and the answer is wrongly true. Only an edge back to a vertex still on the path closes a loop.

Starting DFS only from vertex 0

✗ Wrong
return dfs(0)
✓ Right
return any(state[u] == 0 and dfs(u) for u in range(V))

The graph may be disconnected, or vertex 0 may simply not reach the cycle. With edges [[1, 2], [2, 1]] and V = 3, DFS from 0 finds nothing and returns false.

06

Edge cases

Self-loop u → u

When DFS enters u, state[u] is 1, so the edge u → u is a back edge and the answer is true at once.

Deep graphs in Python

A path of 10⁵ vertices means 10⁵ nested calls, beyond Python's default limit of 1,000. Raise it with sys.setrecursionlimit, or use Kahn's algorithm, which needs no recursion.

07

Complexity

Time
O(V + E)
Space
O(V + E)
Each vertex is entered once and each edge is checked once. The adjacency list takes O(V + E); the state array and the recursion stack take O(V).
08

Cycle detection: DFS vs BFS, directed vs undirected

The right test depends on whether edges have a direction.

GraphMethodA cycle is…
DirectedDFS with 3 states (this page)an edge to a vertex still on the path (state 1)
DirectedKahn's algorithm (BFS on in-degrees)fewer than V vertices ever reach in-degree 0
UndirectedDFS with a parentan edge to a visited vertex that is not the parent
UndirectedUnion-Findan edge whose two ends are already in the same set
09

Detect Cycle in Directed Graph (DFS) FAQ

How do you detect cycle in directed graph using DFS?
  • States: 0 = unvisited, 1 = on the current DFS path, 2 = finished.
  • Enter u: set state[u] = 1.
  • Each edge u → v: if state[v] == 1, a cycle exists (back edge); if 0, recurse; if 2, skip.
  • Leave u: set state[u] = 2.
  • Outer loop: start DFS from every unvisited vertex.
  • Complexity: O(V + E) time and space.
What is a back edge?

An edge into a vertex still on the recursion stack (state 1). DFS sorts every other edge too:

  • Tree edge: into an unvisited vertex (state 0); DFS recurses along it.
  • Forward or cross edge: into a finished vertex (state 2); it cannot close a loop.
  • Back edge: into state 1; it closes a loop.

Only the last kind means a cycle, which is why state 2 is skipped.