Hello! Welcome back to our series on graph algorithms.
In previous lessons, we've explored how to traverse graphs with BFS and DFS and how to find the shortest path in weighted graphs using Dijkstra's algorithm. These are foundational skills for navigating graph structures. Today, we'll shift our focus from finding paths to identifying a key structural property: cycles.
Your learning outcome for this lesson is to detect cycles in undirected graphs using Depth-First Search (DFS). This is a classic interview problem that serves as a building block for more complex tasks, such as determining if a graph is a tree or finding redundant connections in a network.
1. The Challenge of Undirected Cycles
In a DFS traversal, the fundamental sign of a cycle is encountering a node that has already been visited. However, in an undirected graph, this simple rule has a catch.
Consider an edge between nodes A and B. When your DFS moves from A to B, node A is marked as visited. From B, one of its neighbors is A. Since the edge is undirected, you can immediately travel back to A. A is already visited, but A -> B -> A is not what we consider a cycle; it's just traversing an edge back and forth.

So, how do we distinguish a true cycle from simply returning to the node we just came from?
The solution is to keep track of the parent of the current node in the DFS traversal. The "parent" is the node from which we arrived at the current node.
The rule for detecting a cycle then becomes:
During a DFS traversal, if we encounter a neighbor that is already visited and is not the immediate parent of the current node, we have found a cycle.
This parent tracking is the key modification to the standard DFS algorithm for this problem. The article "Detect Cycle in a Graph" explains this distinction clearly.
Detect Cycle in a Graph. Perform Depth First Search(DFS)…
This article from Medium contributor Yumin Lee highlights a crucial point: why the 'white-grey-black' set method (often used for directed graphs) fails for undirected graphs, and why tracking the parent is the correct approach.
Read the section titled 'Detect Cycle in an Undirected Graph', focusing on the paragraphs under 'White-Grey-Black Cycle Detection Does Not Work...' and 'Approach: DFS + Visited Nodes + Parent Node'. This will solidify why the parent-tracking method is necessary.
2. The DFS Algorithm with Parent Tracking
Let's see this logic in action. The core idea is to augment our recursive DFS function to accept two arguments: the current node and its parent.
To get a complete picture of the algorithm, including a step-by-step dry run, watch the following video.
G-12. Detect a Cycle in an Undirected Graph using DFS | C++ | Java
This video from 'take U forward' provides an excellent, detailed walkthrough of the entire process. It covers the intuition, a manual dry run, the algorithm's pseudocode, and how to handle graphs with multiple disconnected components.
Watch from 00:56 to 11:37. Focus on: The core intuition of using a parent node (00:56 - 01:54). The detailed dry run showing how a cycle is found when a visited, non-parent node is encountered (01:54 - 08:26). The pseudocode that formalizes this logic (08:26 - 11:37).
As the video demonstrates, the algorithm can be summarized as follows:
- Initialize a
visitedarray (or set) for all vertices. - Create a main function that iterates through all vertices from
0toV-1. This handles disconnected graphs (graphs with multiple components). - If a vertex
ihas not been visited, start a DFS from it. The initial call will bedfs(i, -1), as the starting node has no parent. - The recursive
dfs(node, parent)function does the following:- Mark
nodeas visited. - For each
neighborofnode:- If
neighborhas not been visited, recursively calldfs(neighbor, node). If that call returnstrue(meaning a cycle was found deeper in the traversal), immediately propagatetrueup the call stack. - If
neighborhas been visited, check ifneighbor != parent. If this is true, you've found a back edge. Returntrue.
- If
- If the loop completes without finding a cycle, return
false.
- Mark
Test your understanding!
Consider this undirected graph represented by an adjacency list:graph = {0:[1, 2], 1:[0, 3], 2:[0, 3], 3:[1, 2]}
You start a DFS from node 0 (dfs(0, -1)). The traversal path is 0 -> 1 -> 3. When you are at node 3, what are its neighbors? Which one will trigger the cycle detection logic and why?
Show answer
At node 3, the neighbors are 1 and 2.
- The parent of
3is1. When the algorithm checks neighbor1, it sees1is visited, but it is also the parent (neighbor == parent), so it does nothing. - When the algorithm checks neighbor
2, it sees that node2is also visited (from the path0 -> 2). Since2is not the parent of3(2 != 1), the condition(visited[neighbor] and neighbor != parent)is met. This detects the cycle0-1-3-2-0, and the function returnstrue.
3. Python Implementation and Complexity
Your background in Python and your goal of preparing for interviews make it crucial to have a clean, standard implementation ready. The following resource provides exactly that.
Cycle Detection in Undirected Graph using DFS - Tutorial
This tutorial page, also from 'takeuforward.org', provides concise code implementations in several languages. We will focus on the Python version, which serves as a perfect template for interviews.
Review the 'Algorithm' section for a quick text summary, then carefully study the Python code provided in the 'Code' section. Note how the main isCycle function handles disconnected components and how the dfs helper function implements the parent-checking logic.
Here's the Python implementation, which directly mirrors the logic we've discussed:
class Solution:
# V: number of vertices
# adj: adjacency list
def isCycle(self, V, adj):
visited = [False] * V
# Loop to handle disconnected components
for i in range(V):
if not visited[i]:
# If DFS from this node finds a cycle, return True
if self.dfs(i, -1, adj, visited):
return True
# If no component has a cycle
return False
def dfs(self, node, parent, adj, visited):
visited[node] = True
for neighbor in adj[node]:
# If neighbor is not visited, recurse
if not visited[neighbor]:
if self.dfs(neighbor, node, adj, visited):
return True
# If neighbor is visited and is NOT the parent
elif neighbor != parent:
# Cycle detected
return True
# No cycle found from this path
return False
Complexity Analysis
Understanding the performance of your algorithms is non-negotiable in an interview setting.
-
Time Complexity:
- Where is the number of vertices and is the number of edges.
- The DFS traversal visits every vertex and every edge exactly once across all components. The outer loop ensures we cover disconnected parts of the graph, but the total work is still proportional to the total number of vertices and edges.
-
Space Complexity:
visitedarray: This takes space.- Recursion Stack: In the worst-case scenario (a long, path-like graph), the depth of the recursion can be up to . Therefore, the call stack requires space.
- The total space complexity is dominated by these factors, resulting in .
The "take U forward" video also provides a good summary of this analysis.
G-12. Detect a Cycle in an Undirected Graph using DFS | C++ | Java
To reinforce the complexity analysis, let's briefly revisit the 'take U forward' video.
Watch the final section on complexity from 17:01 to 18:40.
4. Application: Validating a Tree
A very common interview problem that builds directly on this concept is "Graph Valid Tree" (LeetCode 261). A graph is a valid tree if and only if two conditions are met:
- The graph contains no cycles.
- The graph is fully connected (i.e., it has exactly
V-1edges and all nodes are reachable from a single source).
You now know how to solve the first part of this problem.
Graph Valid Tree - Leetcode 261 - Python
The NeetCode video for 'Graph Valid Tree' is a great example of applying this pattern. It uses the exact same dfs(node, previous) logic to detect cycles.
Watch the explanation of the algorithm from 03:18 to 06:13. Notice how he describes the need for a 'previous' node parameter to avoid false positives—this is the same as our 'parent' node logic.
Conclusion
In this lesson, you've added a crucial graph algorithm to your interview arsenal. Detecting cycles is a fundamental problem, and the DFS-based approach for undirected graphs is the standard, efficient solution.
Key Takeaways:
- A simple "is visited?" check is not enough to detect cycles in an undirected graph because of bidirectional edges.
- The correct approach is to use DFS while tracking the parent of each node in the traversal path.
- A cycle is detected when you encounter a visited neighbor that is not the node's immediate parent.
- To handle disconnected graphs, a loop in the main function must attempt a DFS from every unvisited vertex.
- The algorithm runs in time and requires space.
Preview of the Next Lesson:
What about cycles in directed graphs? The parent trick won't work there, because an edge A -> B doesn't imply an edge B -> A. An edge from a node to a previously visited node could be a cross-edge to a different branch, not necessarily a back-edge forming a cycle. In the next lesson, we will explore how to detect cycles in directed graphs using DFS, which requires a more nuanced three-state tracking system (visiting, visited, unvisited).