Pattern #52
Cycle Detection
Important interview questions, thinking patterns, Undirected Parent Rule, Directed 3-State Array (0, 1, 2), Union Find Cycle Detection, Fast & Slow Pointers, and Go templates.
Must Solve
15 core questions — solve these first.
- 1.Detect Cycle in an Undirected GraphUndirected DFS: check visited[neighbor] && neighbor != parentmedium
- 2.Detect Cycle in a Directed Graph3-State Array (0=unvisited, 1=visiting, 2=finished); edge to state 1 implies back-edge cyclemedium
- 3.Course ScheduleDirected Cycle Detection / Kahn's Algorithm: deadlock cycle implies impossible course orderingmedium
- 4.Course Schedule IITopological Sort via 3-State DFS or Kahn's Queue; detect cycle or return topological ordermedium
- 5.Graph Valid TreeUndirected Graph: components == 1 AND no cycles (or edges == n - 1)medium
- 6.Redundant ConnectionUnion Find: find first edge where find(u) == find(v)medium
- 7.Find Eventual Safe States3-State Directed Cycle DFS: terminal nodes and nodes not leading to cycles are safemedium
- 8.Is Graph Bipartite?DFS 2-Coloring: odd-length cycle causes color conflictmedium
- 9.Detect Cycle Using Union FindIterate edge list; if Find(u) == Find(v) before union, cycle existsmedium
- 10.Find the Duplicate NumberFunctional Graph: array values act as pointers i -> nums[i]; Floyd's Fast & Slow pointersmedium
- 11.Circular Array LoopFunctional Graph DFS / Fast & Slow pointers checking same-direction jumpsmedium
- 12.Linked List CycleFloyd's Fast & Slow Pointers (slow=1 step, fast=2 steps)easy
- 13.Linked List Cycle IIFast & Slow collision -> reset slow to head & move both 1 step to find cycle entrymedium
- 14.Alien DictionaryCharacter Dependency Directed Graph + 3-State Cycle Detection + Topological Sorthard
- 15.Minimum Height Trees / Tree Validation VariantsKahn's leaf trimming / tree cycle validationmedium
Also Important
7 more questions worth practicing.
- 16.Find All Possible Recipes from Given SuppliesRecipe Dependency Graph + Kahn's Topological Sort / Cycle Detectionmedium
- 17.Parallel CoursesKahn's BFS level-by-level semester counting with cycle detectionmedium
- 18.Remove Invalid DependenciesDirected Graph Cycle Removal / Feedback Arc Set variantshard
- 19.Detect Cycle in Dependency Graph3-State Directed DFS on package/microservice dependency graphsmedium
- 20.Strongly Connected ComponentsTarjan's or Kosaraju's algorithm for directed graph strongly connected component cycleshard
- 21.Functional Graph Cycle ProblemsGraphs where out-degree of every node is 1; cycle entry & length calculationmedium
- 22.Longest Cycle in a GraphFunctional Graph DFS tracking node distance from current search roothard
How to Think
- Undirected graph?DFS/BFS + Parent check (visited && neighbor != parent)
- Directed graph?DFS + 3-State Array (0=unvisited, 1=visiting, 2=finished)
- Edges arriving one by one?Union Find (Find(u) == Find(v) before union)
- Every node points to 1 next node?Fast & Slow Pointers (Floyd's algorithm)
Go Cycle Detection Templates
Undirected DFS Cycle, Directed 3-State DFS Cycle & Union Find Cycle in Go
// 1. Undirected Graph DFS Cycle Detection: O(V + E) Time
func hasCycleUndirected(n int, adj [][]int) bool {
visited := make([]bool, n)
var dfs func(u, parent int) bool
dfs = func(u, parent int) bool {
visited[u] = true
for _, v := range adj[u] {
if !visited[v] {
if dfs(v, u) { return true }
} else if v != parent {
return true // Visited neighbor AND not parent -> Cycle!
}
}
return false
}
for i := 0; i < n; i++ {
if !visited[i] && dfs(i, -1) { return true }
}
return false
}
// 2. Directed Graph 3-State DFS Cycle Detection: O(V + E) Time
// States: 0 = Unvisited, 1 = Visiting (active stack), 2 = Finished
func hasCycleDirected(n int, adj [][]int) bool {
state := make([]int, n)
var dfs func(u int) bool
dfs = func(u int) bool {
if state[u] == 1 { return true } // Back Edge Cycle!
if state[u] == 2 { return false } // Fully processed -> Safe!
state[u] = 1 // Mark visiting
for _, v := range adj[u] {
if dfs(v) { return true }
}
state[u] = 2 // Mark finished
return false
}
for i := 0; i < n; i++ {
if state[i] == 0 && dfs(i) { return true }
}
return false
}
// 3. Union Find Cycle Detection (Redundant Connection): O(E · α(V)) Time
func findRedundantConnection(edges [][]int) []int {
n := len(edges)
parent := make([]int, n+1)
for i := 1; i <= n; i++ { parent[i] = i }
var find func(i int) int
find = func(i int) int {
if parent[i] != i { parent[i] = find(parent[i]) }
return parent[i]
}
for _, edge := range edges {
u, v := edge[0], edge[1]
rootU, rootV := find(u), find(v)
if rootU == rootV {
return edge // Endpoints already connected -> Redundant Cycle Edge!
}
parent[rootV] = rootU
}
return nil
}👉 Undirected/Directed DFS: O(V + E) | Union Find: O(E · α(V)) ≈ O(E)
Undirected Parent Rule vs Directed 3-State Array
1. Undirected Parent Check: In an undirected graph, edge u - v goes both ways. Passing the parent node handles the edge you came from. Seeing a visited node where neighbor != parent reveals a cycle.
2. Directed 3-State Array: A simple boolean array fails for directed graphs! Reaching a finished node (state = 2) from a different branch is NOT a cycle. A cycle exists ONLY when an edge points to a node in the current active call stack (state = 1 Visiting).
Union Find & Fast & Slow Pointer Cycles
1. Union Find (Undirected Edges Arriving Incrementally): When processing edge u - v, if Find(u) == Find(v) before union, u and v are already connected. Adding edge u - v creates a cycle (Redundant Connection).
2. Functional Graph Fast & Slow Pointers: When every node has exactly 1 outgoing pointer (Linked List Cycle, Find Duplicate Number, Circular Array Loop), Floyd's Fast & Slow pointers (slow = 1 step, fast = 2 steps) detect cycles in O(1) space upon collision.
Undirected Cycle → Visited neighbor && neighbor != parent
Directed Cycle → Edge to state 1 (Visiting / Active Stack)
3-State Array → 0=unvisited, 1=visiting, 2=finished
Union Find Cycle → Find(u) == Find(v) before union
Fast & Slow → Functional graph (out-degree 1) cycle collision💡 Golden Rule: "In an undirected graph, ignore the edge you came from; in a directed graph, a cycle exists when you reach a node that is still inside the current DFS path."
Common Interview Mistakes
The parent edge trick is exclusively for undirected graphs! Directed graphs require 3-state tracking (0, 1, 2).
Reaching a finished node from an earlier branch is NOT a cycle. A cycle exists only when reaching a node currently visiting (state 1).
A cycle might be located in component #3! Always run an outer loop over all 0..n-1 nodes.
Standard Union Find does NOT preserve directed edge directions and will return false positive cycles on DAGs! Use 3-State DFS or Kahn's algorithm.
Interview Rules
- 1. Undirected graph cycle? → Visited + Parent check (
visited[neighbor] && neighbor != parent) - 2. Directed graph cycle? → 3-State Array (
0=unvisited, 1=visiting, 2=finished). Edge to state 1 = cycle - 3. Course Schedule / Dependency Deadlock?→ Directed Cycle Detection or Kahn's Topological Sort
- 4. Incremental undirected edges? → Union Find (
Find(u) == Find(v)before union) - 5. Functional graph (out-degree 1)?→ Floyd's Fast & Slow pointers (slow=1, fast=2)
- 6. Graph Valid Tree? → Components == 1 AND no cycles (or edges == n - 1)
- 7. Disconnected Graph? → Outer loop over all
0..n-1nodes - 8. Overall Complexity → Traversal Time:
O(V + E)| Union Find Time:O(E · α(V))
Small Rules
- Rule 1: Undirected cycle requires ignoring the parent edge you arrived from.
- Rule 2: Directed cycle requires edge to a node in state 1 (visiting/active stack).
- Rule 3: State 2 (finished) nodes are fully processed and safe.
- Rule 4:Kahn's algorithm leaves unprocessed nodes when a cycle is present.
- Rule 5: Union Find detects undirected cycles when edge endpoints share a root.
Production Thinking
Microservice Dependency Deadlocks → Detecting circular service call chains (A -> B -> C -> A) preventing startup deadlocks
Build System Dependency Checks → Verifying package dependency graphs (Bazel, npm, Cargo) are acyclic before compilation
Workflow Engine Cycle Prevention → Validating approval workflow state machines to prevent infinite approval loops
Object Reference Memory Leaks → Detecting cyclic object references in garbage collector graph sweeps
Remember This
Cycle Detection
Undirected → Visited + Parent check
Visited != parent → Cycle
Directed → 0 / 1 / 2 states
Edge to Visiting → Cycle
Kahn's BFS → Nodes left unprocessed = Cycle
Union Find → Same root before union = Cycle
Fast & Slow → Functional graph (out-degree 1)
Time → O(V + E)💡 Golden Rule: "In an undirected graph, ignore the edge you came from; in a directed graph, a cycle exists when you reach a node that is still inside the current DFS path."