Hello! Welcome to your next lesson on graph algorithms.
In previous lessons, you mastered the two fundamental graph traversal algorithms: Depth-First Search (DFS) and Breadth-First Search (BFS). You saw how DFS uses a stack (via recursion) to go deep into the graph, while BFS uses a queue to explore layer-by-layer.
Today, we'll build directly on your knowledge of DFS. Your learning outcome is to detect cycles in both directed and undirected graphs using DFS. A cycle is a path in a graph that starts and ends at the same vertex. Detecting them is crucial in many real-world software engineering scenarios, such as finding dependency loops in build systems, identifying deadlocks in concurrent processes, or validating workflows. This is a key skill for developing the "architectural mindset" you're aiming for.
We will tackle this in two parts, as the technique for detecting cycles is different for undirected and directed graphs.
1. Cycle Detection in Undirected Graphs
In an undirected graph, edges are two-way. A cycle is a closed loop where you can traverse from a node back to itself without immediately backtracking along the same edge you just used.

The basic idea is to use DFS and keep track of visited nodes. If our DFS traversal encounters a node that has already been visited, we might have found a cycle. However, there's a catch.
Consider a simple graph A -- B. If we start a DFS from A, we visit A, then move to its neighbor B. From B, one of its neighbors is A, which is already visited. Is this a cycle? No, it's just the two-way nature of the edge.
To solve this, our DFS algorithm needs to know not just which nodes are visited, but also how it arrived at the current node. We can do this by keeping track of the parent of each node in the traversal path.
The Rule: During a DFS traversal, if we encounter a visited neighbor that is not the immediate parent of the current node, we have found a cycle.
This video provides an excellent, detailed walkthrough of this exact logic.
G-12. Detect a Cycle in an Undirected Graph using DFS | C++ | Java
The channel 'take U forward' clearly explains the intuition behind using a 'parent' node to correctly identify cycles in undirected graphs. This walkthrough will solidify the algorithm in your mind.
Watch from 00:56 to 11:37. Pay close attention to how the parent parameter is passed in the recursive DFS calls and used to differentiate between a true back-edge (a cycle) and simply traversing back to the node you just came from.
The Algorithm in a Nutshell
For each vertex in the graph (to handle disconnected components):
- If the vertex has not been visited, start a recursive DFS from it. Pass an initial parent value that's impossible (like -1).
- In the recursive
dfs(node, parent)function:
a. Marknodeas visited.
b. For eachneighborofnode:
i. Ifneighborhas not been visited, recursively calldfs(neighbor, node). If this recursive call returnstrue(meaning a cycle was found deeper in the graph), immediately returntrue.
ii. Else if theneighboris not theparent, you've found a cycle! Returntrue. - If the DFS completes for a component without returning
true, no cycle was found in that component. If all components are checked and no cycle is found, the graph is acyclic.
Complexity
As this is essentially a standard DFS traversal, the complexity remains the same:
- Time Complexity: , where V is the number of vertices and E is the number of edges. We visit each vertex and edge once.
- Space Complexity: for the visited array and the recursion stack space in the worst case (for a path-like graph).
Test your understanding!
Consider this undirected graph:
- 0: [1, 2]
- 1: [0, 3]
- 2: [0, 3]
- 3: [1, 2]
Starting a DFS from node 0 (and exploring neighbors in increasing order), trace the algorithm. Does it find a cycle? If so, at which step is the cycle detected?
Show answer
Yes, a cycle is detected. The cycle is 0-1-3-2-0.
Trace:
dfs(0, -1): Mark 0 as visited.- Explore neighbors of 0. First is 1.
dfs(1, 0): Mark 1 as visited.- Explore neighbors of 1. First is 0. But 0 is the parent, so we ignore it. Next neighbor is 3.
dfs(3, 1): Mark 3 as visited.- Explore neighbors of 3. First is 1. But 1 is the parent, so we ignore it. Next neighbor is 2.
dfs(2, 3): Mark 2 as visited.- Explore neighbors of 2. First is 0.
- Node 0 is visited.
- Node 0 is not the parent of the current node (the parent of 2 is 3).
- Cycle detected! The function
dfs(2, 3)returnstrue. Thistruevalue propagates all the way up the call stack.
2. Cycle Detection in Directed Graphs
Now let's turn to directed graphs. Can we use the same parent-checking logic? The answer is no. The problem in directed graphs is different.

Consider a graph where 1 -> 2 and 1 -> 3 -> 2. If we start DFS from 1, we might explore 1 -> 3 -> 2. Now nodes 1, 3, and 2 are marked visited. When we backtrack to 1 and explore the path 1 -> 2, we find that 2 is already visited. But this is not a cycle. It's just two different paths leading to the same node.
The key insight for directed graphs is that a cycle exists if and only if the DFS traversal encounters a "back edge"—an edge that leads from the current node to a node that is an ancestor in the current DFS traversal path.
To implement this, we need to track not just which nodes have been visited globally, but which nodes are currently in the recursion stack.
The Solution: We use two boolean arrays:
visited[]: Tracks nodes that have been visited at any point. This prevents redundant processing of the same node across different DFS paths.pathVisited[](also calledonStack[]orrecursionStack[]): Tracks nodes that are currently in the recursion path of the active DFS traversal.
The Rule: During a DFS, if we encounter a neighbor that is already in the pathVisited[] array, we have found a cycle.
This video first explains why the undirected method fails and then walks through the two-array solution.
G-19. Detect cycle in a directed graph using DFS | Java | C++
This video from 'take U forward' is crucial. It first demonstrates why the simpler parent-check fails for directed graphs, then introduces the two-array (visited and pathVisited) technique which is the standard solution.
Watch from the beginning to 12:17. The first part (until 04:39) explains the problem, and the rest details the correct algorithm with a clear step-by-step example. Focus on when a node is added to pathVisited and, critically, when it's removed.
The Algorithm in a Nutshell
- Initialize a
visitedarray and apathVisitedarray to allfalse. - For each vertex
vin the graph:
a. Ifvis notvisited, calldfs_check(v). - In the recursive
dfs_check(node)function:
a. Marknodeastruein bothvisitedandpathVisited.
b. For eachneighborofnode:
i. Ifneighboris notvisited, calldfs_check(neighbor). If it returnstrue, a cycle was found, so returntrueimmediately.
ii. Else ifneighboris already markedtrueinpathVisited, we've hit an ancestor in the current path. Cycle detected! Returntrue.
c. Before the function returns, un-marknodeinpathVisited(set it tofalse). This is the backtracking step, signifying thatnodeis no longer in the current recursion path.
d. Returnfalse(no cycle found from this node).
For a good textual summary and Java code examples, this article is a helpful supplement.
Detecting Cycles in Graphs with Depth First Search in Java
This article from Medium provides a clear textual explanation of the logic, using the terms 'unvisited,' 'visiting' (our pathVisited), and 'visited.' Seeing the implementation in Java will be familiar to you as a software engineer.
Read the sections 'How Depth First Search Detects Cycles', 'Why Recursion Works for Directed Graphs', and 'Time and Space Complexity'. Notice how the onStack boolean array in their code serves the exact same purpose as the pathVisited array in the video.
Conclusion
You have now learned how to adapt DFS to solve the critical problem of cycle detection. The key is understanding that the definition of a "back edge" differs between undirected and directed graphs, requiring two distinct approaches.
Key Takeaways:
- Cycle detection is a powerful application of DFS.
- For undirected graphs, a cycle is found when DFS hits a visited node that is not its immediate parent. The algorithm is a DFS that passes a
parentreference. - For directed graphs, a cycle is found when DFS hits a node that is currently in the recursion stack. The algorithm uses two arrays:
visitedandpathVisited(oronStack). - In both cases, the time complexity is because the underlying algorithm is still DFS.
Preview of the Next Lesson:
Our work with directed graphs and cycles leads directly to our next topic: Topological Sorting. This algorithm orders the vertices of a Directed Acyclic Graph (DAG) such that for every directed edge from vertex u to vertex v, u comes before v in the ordering. As the name implies, it only works on graphs with no cycles. You've just learned how to verify that prerequisite