AlgoMaster Logo

Cycle Detection in Undirected Graphs

Medium Priority8 min readUpdated May 30, 2026
Listen to this chapter
Unlock Audio

Cycle detection in undirected graphs comes down to a few key questions: how do you avoid false positives from traversing an edge you came from, how do you handle disconnected components, and what changes when you use Union-Find instead of DFS?

This chapter covers three approaches to detect cycles in an undirected graph: DFS with parent tracking, Union-Find, and BFS with parent tracking.

What Is a Cycle in an Undirected Graph?

A cycle in an undirected graph is a path that starts at some vertex, travels through one or more other vertices (each connected by edges), and returns to the starting vertex without repeating any edge.

More precisely, a cycle is a sequence of vertices v0, v1, v2, ..., vk where:

  • v0 = vk (the path loops back to the start)
  • k >= 3 (we need at least 3 distinct vertices)
  • All vertices except the first and last are distinct
  • Each consecutive pair is connected by an edge

That third point is important. In an undirected graph, going from A to B and immediately back to A does not count as a cycle. That is just traversing the same edge twice. A real cycle requires at least three vertices forming a closed loop.

Here is a graph that contains a cycle:

In this graph, B - C - D - B forms a cycle. The vertices B, C, and D create a closed loop. Meanwhile, vertex E is connected to A but is not part of any cycle.

And here is a graph with no cycle (a tree):

No matter which vertex you start from, there is exactly one path to every other vertex. No closed loops, no cycles. This is what makes it a tree.

Edge Cases: Self-Loops and Multi-Edges

The textbook definition above assumes a simple graph (no self-loops, no parallel edges). Real inputs sometimes include these, and they change what counts as a cycle and which algorithms still work.

Self-Loops

A self-loop is an edge from a vertex to itself, like v - v. By any reasonable definition, that is a cycle of length 1. Detecting it is trivial: while iterating edges (Union-Find) or while iterating a vertex's adjacency list (DFS/BFS), check whether u == v. If yes, you have a cycle without doing any traversal.

DFS with parent tracking will also flag a self-loop correctly. When you reach v and scan its neighbors, you encounter v itself; v is visited, and the parent of v is some other vertex (or -1 for the root), so neighbor == parent is false, and the algorithm reports a cycle.

Parallel Edges (Multi-Edges)

A multi-edge is two or more distinct edges between the same pair of vertices. Two parallel edges between A and B form a cycle of length 2 (go A → B on one edge, B → A on the other).

DFS with parent tracking fails on multi-edges. The standard parent-skip rule says "ignore the neighbor that equals the parent, because that is the edge you just traversed." With multi-edges, both copies of the edge to the parent satisfy neighbor == parent, so the algorithm skips both and never reports the cycle.

There are two ways to fix this:

  • Track edge identity, not vertex identity. Give every edge a unique ID and skip the specific edge you arrived on, rather than every edge whose other endpoint is the parent.
  • Use Union-Find on the edge list. Union-Find treats every edge independently. If you process the two parallel A-B edges in order, the first union connects A and B; when you try to union them again on the second edge, the algorithm reports a cycle correctly.

If your input is guaranteed to be a simple graph (the usual assumption in interview problems), neither edge case matters. If the input could contain self-loops or parallel edges, prefer Union-Find or switch DFS to edge-ID tracking.

Approach 1: DFS with Parent Tracking

DFS is the most direct way to detect cycles in an undirected graph.

The Core Idea

When you run DFS on an undirected graph, you explore as deep as possible along each branch before backtracking. You mark vertices as visited so you do not process them again. If during DFS you encounter a neighbor that is already visited, AND that neighbor is not the vertex you just came from (the parent), then you have found a cycle.

Why do we need the parent check? Because in an undirected graph, every edge goes both ways. When you travel from vertex A to vertex B, vertex B's adjacency list includes A. If you do not track the parent, you would see A as "already visited" and incorrectly report a cycle.

In this diagram, we are at B (current vertex). A is the parent (we came from A). C is visited but is NOT the parent. This means there is another path from B to C that does not go through the edge we are currently on. That means there is a cycle.

To handle disconnected components, run DFS from every unvisited vertex.

Implementation

Complexity

  • Time: O(V + E), where V is the number of vertices and E is the number of edges. We visit each vertex once and examine each edge twice (once from each direction).
  • Space: O(V) for the visited array and the recursion stack.

Approach 2: Union-Find (Disjoint Set Union)

Union-Find takes a different approach. Instead of traversing the graph, it processes edges one at a time and checks whether the two endpoints are already connected.

The Core Idea

Start with every vertex in its own set. For each edge (u, v), check if u and v belong to the same set.

  • If they are in different sets, merge them. This edge connects two previously disconnected components. No cycle.
  • If they are in the same set, adding this edge would create a cycle. The two vertices are already connected through some other path, so this new edge closes a loop.

Union-Find reduces cycle detection to a membership query.

Implementation (with Path Compression and Union by Rank)

Path compression and union by rank bring the amortized cost of each operation down to nearly O(1). Path compression flattens the tree by pointing each node directly at the root during a find, and union by rank attaches the shorter tree under the taller one to keep trees shallow.

Complexity

  • Time: O(E * alpha(V)), which is essentially O(E) for all practical purposes.
  • Space: O(V) for the parent and rank arrays.

Approach 3: BFS with Parent Tracking

The BFS approach works on the same principle as DFS with parent tracking, using a queue instead of recursion. If during BFS we encounter a visited neighbor that is not the parent of the current vertex, we have found a cycle.

Implementation

BFS has the same time and space complexity as DFS: O(V + E) time and O(V) space. The main practical difference is that BFS uses an explicit queue and avoids recursion, which can be helpful if the graph is very deep and you are worried about stack overflow.

Comparison of Approaches

AspectDFS + ParentUnion-FindBFS + Parent
Time ComplexityO(V + E)O(E * alpha(V))O(V + E)
Space ComplexityO(V)O(V)O(V)
Input FormatAdjacency listEdge listAdjacency list
Handles Disconnected GraphsYes (loop over all vertices)Yes (inherently)Yes (loop over all vertices)
Can Identify the Cycle EdgeHarderEasy (the edge that fails union)Harder
Recursion RequiredYes (or use explicit stack)NoNo
Best WhenGeneral-purpose, adjacency list inputEdge list input, dynamic edgesNeed iterative solution
Interview FrequencyVery commonCommonRare

For most interview problems, DFS is the default choice. Use Union-Find when the problem gives you an edge list, asks you to find the specific edge causing the cycle, or involves incrementally adding edges.

Key Takeaways

  • A cycle in an undirected graph requires reaching an already-visited vertex that is not the parent you arrived from, because every undirected edge appears in both endpoints' adjacency lists and would otherwise trigger a false positive.
  • A valid cycle in a simple graph needs at least three distinct vertices forming a closed loop, so traveling from one vertex to a neighbor and immediately back along the same edge does not count.
  • This chapter covers three approaches: DFS with parent tracking, Union-Find, and BFS with parent tracking. DFS and BFS traverse an adjacency list, while Union-Find processes an edge list and flags a cycle when an edge connects two vertices that already share the same set.
  • Self-loops and parallel edges are edge cases that count as cycles, and DFS with the standard parent-skip rule misses parallel edges unless you track edge identity, while Union-Find handles both cases correctly on the edge list.
  • All three approaches handle disconnected graphs: DFS and BFS run from every unvisited vertex, and Union-Find covers every component because it processes all edges regardless of connectivity.
  • DFS and BFS both run in O(V + E) time with O(V) space, and Union-Find runs in near O(V + E) with path compression and union by rank keeping each operation close to constant time.

Quiz

Cycle Detection in Undirected Graphs Quiz

10 quizzes