Cycle Detection

A cycle in a graph is a closed path that starts and ends at the same vertex. Cycle detection is a fundamental graph algorithm with wide practical applications — from identifying deadlocks in operating systems to validating dependency graphs in package managers.

What Is a Cycle?#

No Cycle (Tree)
Rendering diagram...
Has Cycle
Rendering diagram...

In the first diagram, every pair of vertices is connected by exactly one path — this is a tree, which is always cycle-free. In the second, vertices B and C are each reachable from A by two distinct paths, forming a cycle: A → B → C → A (equivalently, A → C → B → A going the other way).

Why Detect Cycles?#

ApplicationWhy Cycles Matter
Deadlock DetectionProcesses waiting in a cycle = deadlock
Dependency ResolutionCircular dependencies are invalid (npm, pip)
Course PrerequisitesCircular prerequisites are impossible
Compiler OptimizationLoops in control flow graphs
Garbage CollectionReference cycles need special handling
Topological SortOnly possible on acyclic graphs (DAGs)

Cycle Detection: Undirected vs Directed#

The two graph types require fundamentally different strategies. In an undirected graph, edges have no direction — any path you traverse is reversible, so any revisited vertex (other than the one you immediately came from) signals a cycle. In a directed graph, direction matters: reaching a previously visited vertex is sometimes perfectly valid because multiple directed paths can legitimately converge on the same destination. A cycle only exists if the edge leads back into the current active path, not just any vertex seen before. This difference demands a more precise tracking mechanism for directed graphs:

Graph TypeKey InsightApproach
UndirectedFinding a visited vertex that is not the parent indicates a cycleDFS with parent tracking
DirectedFinding an edge to a vertex currently in the recursion stack indicates a cycleDFS with color marking (white/gray/black)

Cycle Detection in Undirected Graphs#

In an undirected graph, a cycle is present when DFS encounters an already-visited vertex that is not the direct parent of the current vertex. The reasoning is straightforward: if a neighbor is already visited and it is not the vertex we came from, then there must be at least two distinct paths connecting them — one through the DFS tree we have been building, and one through the edge we just found. Two paths between the same pair of vertices in an undirected graph always form a cycle.

Cycle Detection in Undirected Graph
Rendering diagram...

Why Track Parent?#

In an undirected graph, every edge A—B is stored in both directions: A's adjacency list contains B, and B's adjacency list contains A. Without parent tracking, DFS would report a cycle every time it looks back at the vertex it just came from — producing a false positive on every single edge.

Rendering diagram...
Rendering diagram...

Cycle Detection in Undirected Graph

Detect if an undirected graph contains a cycle using DFS with parent tracking

Time:O(V + E)Space:O(V)Best:O(1) if cycle at startWorst:O(V + E)

Cycle Detection in Directed Graphs#

Directed graphs require a more nuanced approach. In a directed graph, it is perfectly normal to reach a vertex that was already visited in an earlier DFS branch — multiple directed paths can legitimately converge on the same destination. Reaching a previously visited vertex does not always indicate a cycle; the edge could be a forward edge or a cross edge, neither of which forms a cycle. What matters is detecting back edges: edges that point to a vertex that is still on the current recursion stack, meaning the path has looped back into itself.

Edge Types in Directed DFS
Rendering diagram...
Rendering diagram...

The Three Colors#

ColorStateMeaning
WHITEUnvisitedVertex hasn't been explored yet
GRAYIn ProgressVertex is in current DFS path (recursion stack)
BLACKCompletedVertex and all descendants fully explored

Two states — visited and unvisited — are not sufficient for directed graphs. A "visited" vertex might have been fully processed in an earlier, unrelated DFS branch, which is not a cycle. The GRAY state is what allows us to distinguish "currently on the active path" from "already finished elsewhere." Only an edge into a GRAY vertex closes a loop in the active path.

Key insight: A cycle exists precisely when DFS encounters an edge leading to a GRAY vertex — a back edge that closes a loop within the current DFS path.

Rendering diagram...
Rendering diagram...

Cycle Detection in Directed Graph

Detect if a directed graph contains a cycle using DFS with color marking

Time:O(V + E)Space:O(V)Best:O(1) if cycle at startWorst:O(V + E)

Alternative: Union-Find for Undirected Graphs#

For undirected graphs, Union-Find (Disjoint Set Union) offers a clean alternative to DFS. The idea is to process edges one at a time: if both endpoints already belong to the same connected component, adding the new edge would create a cycle. Note that Union-Find only applies to undirected graphs — it has no concept of edge direction and cannot detect directed cycles.

Rendering diagram...

Cycle Detection with Union-Find

Detect cycles in undirected graphs using Disjoint Set Union

Union-Find approach for undirected cycle detection

Comparison of Approaches#

α(V) is the inverse Ackermann function, nearly constant

ApproachGraph TypeTimeSpaceBest For
DFS with ParentUndirectedO(V + E)O(V)General undirected graphs
DFS with ColorsDirectedO(V + E)O(V)General directed graphs
Union-FindUndirectedO(E × α(V))O(V)Edge-list representation

Edge Classification in Directed Graphs#

DFS on a directed graph produces four distinct edge types. Understanding them clarifies why only one type — the back edge — signals a cycle:

DFS Edge Classification
Rendering diagram...
Edge TypeFrom → ToDetectionIndicates Cycle?
Tree EdgeParent → Child in DFS treeTarget is WHITENo
Back EdgeDescendant → AncestorTarget is GRAYYes!
Forward EdgeAncestor → Descendant (non-tree)Target is BLACKNo
Cross EdgeBetween unrelated subtreesTarget is BLACKNo

Reviewing AI-Generated Code#

A solid grasp of cycle detection helps you spot subtle errors in AI-generated code — mistakes that look plausible at a glance but fail on edge cases or when the algorithm is applied to the wrong graph type:

What to Verify#

IssueWhat to CheckCommon AI Mistake
Graph TypeUse different algorithms for directed vs undirected graphsUsing parent tracking for directed graphs
Parent TrackingSkip parent in undirected cycle checkFalse positive: treating parent as cycle
Color MarkingAn edge from the current vertex to a GRAY neighbor signals a cycleOnly checking visited, not in-progress (GRAY vs BLACK distinction missed)
All ComponentsCheck all vertices for disconnected graphsOnly checking from one start vertex
Edge to SelfSelf-loops are cycles if they matterNot handling self-loops

Common AI Pitfalls#

Cycle Detection Bugs

Common mistakes AI makes in cycle detection

Correct undirected cycle detection with parent tracking

Questions to Ask AI#

When AI generates cycle detection code, probe it with targeted questions:

  • Does this algorithm target directed or undirected graphs?
  • Why use color marking instead of a simple visited set?
  • How does this handle disconnected graphs?
  • Can you extend this to return the actual cycle path, not just a boolean?

Summary#

ConceptKey Takeaway
CyclePath that starts and ends at the same vertex
Undirected DetectionDFS with parent tracking — finding a visited non-parent vertex indicates a cycle
Directed DetectionDFS with colors — encountering a GRAY neighbor (one still on the current path) is a back edge and signals a cycle
Union-FindAlternative for undirected graphs — adding an edge within the same component creates a cycle
Back EdgeOnly type of edge that indicates a cycle in directed graphs
ApplicationsDeadlock detection, dependency validation, topological sort feasibility

Cycle detection is a cornerstone of graph algorithm design. Mastering it unlocks a range of downstream capabilities: verifying that topological sort is applicable, validating dependency graphs, detecting deadlocks, and reasoning about control flow. The three techniques covered here — DFS with parent tracking, three-color marking, and Union-Find — each represent a distinct mental model. Knowing when and why to reach for each one is what separates a solid understanding of graphs from surface-level familiarity.