Hello! Welcome to your next lesson on graph algorithms.
In our last session, we mastered how to detect cycles in undirected graphs using DFS by tracking a parent node. This was crucial to avoid mistaking a simple bidirectional edge traversal for a true cycle.
Today, we'll tackle a related but distinct challenge: detecting cycles in directed graphs using DFS. This is a fundamental problem with direct applications in areas like task scheduling, dependency resolution, and deadlock detection—making it a favorite in technical interviews.
As you'll soon see, the parent-tracking trick is no longer sufficient. Directed graphs require a more sophisticated way to distinguish between different types of edges encountered during a traversal.
1. Why the Parent-Tracking Method Fails for Directed Graphs
In an undirected graph, if we're at node B and see a visited neighbor A from which we just came, it's not a cycle. It's just the other direction of the same edge.
In a directed graph, an edge A -> B does not imply an edge B -> A. If our DFS path is C -> A -> B, and from B we see an edge B -> A, this is a true cycle (A -> B -> A). The concept of a "parent" in the traversal doesn't help us distinguish a legitimate cycle from other path types.
A more complex scenario arises with "cross-edges." Imagine our DFS explores one branch completely (e.g., 0 -> 1 -> 2), and then backtracks to explore another branch (0 -> 3). If there's an edge from 3 to the already-visited node 1, is this a cycle? No, it's a cross-edge connecting two separate branches of the DFS tree. A simple visited check would incorrectly flag this as a cycle.
To understand this distinction visually and intuitively, the following video provides an excellent explanation.
G-19. Detect cycle in a directed graph using DFS | Java | C++
This video from 'take U forward' starts by perfectly illustrating why the algorithm for undirected graphs is not suitable here. It demonstrates a case where a DFS encounters a visited node that does not form a cycle.
Watch from the beginning to 04:39. Pay close attention to the example graph and the dry run. The key takeaway is understanding why visiting an already-visited node is not sufficient proof of a cycle in a directed graph. The video introduces the crucial concept of being 'on the same path'.
2. The Two-State Tracking System: visited and recursion stack
The core insight from the video is that we need to detect when we loop back to a node that is an ancestor in the current path of exploration. We need to differentiate between:
- A node visited in a previous, completed DFS branch.
- A node visited in the current, active DFS branch.
To achieve this, we use two boolean arrays (or sets):
visited[]: This array tracks every node that has ever been visited. Once a node is markedTrue, it staysTrue. This helps us avoid re-processing nodes and entire subgraphs that we know are cycle-free.recursionStack[](also calledpathVisited[]orvisiting[]): This array tracks only the nodes that are currently in the recursion call stack for the active DFS traversal. A node is markedTruewhen we enter its DFS function and markedFalseright before we exit (i.e., after exploring all its neighbors).
The rule for detecting a cycle is now simple and robust:
If our DFS traversal from a node
uencounters a neighborvthat is already in therecursionStack(recursionStack[v] == True), we have found a back edge. This indicates a cycle.

The Three-Color Analogy
This two-array system is often described using a "three-color" analogy, which is great for conceptual understanding:
- WHITE: Unvisited node. (
visited[i] == FalseandrecursionStack[i] == False) - GRAY: Node is currently being visited (in the
recursionStack). (recursionStack[i] == True) - BLACK: Node and all its descendants have been fully visited. (
visited[i] == TrueandrecursionStack[i] == False)
A cycle is found whenever the DFS encounters a GRAY node.
The "take U forward" video continues with a fantastic, detailed walkthrough of this exact algorithm.
G-19. Detect cycle in a directed graph using DFS | Java | C++
Continuing with the same video, we'll now see a full dry run of the two-array algorithm. It demonstrates how the visited and pathVisited arrays are updated during traversal and backtracking, first on a graph with no cycle, and then on one with a cycle.
Watch from 04:39 to 12:17. This is the most critical part of the lesson. 04:39 - 05:56: Introduction of the two arrays: visited and pathVisited. 05:56 - 10:43: A detailed walkthrough on a graph without a cycle, showing how pathVisited is marked false upon backtracking. 10:43 - 12:17: A walkthrough on a graph with a cycle, showing the exact moment a cycle is detected because a neighbor is found in the pathVisited array.
3. Python Implementation and a Classic Interview Problem
With the logic clear, let's look at a standard Python implementation. This pattern is essential for your interview toolkit. The code below is adapted from the common structure for this problem.
def isCyclic(V, adj):
visited = [False] * V
recursion_stack = [False] * V
# This loop handles disconnected components in the graph
for node in range(V):
if not visited[node]:
if dfs(node, visited, recursion_stack, adj):
return True # Cycle found
return False # No cycle found in any component
def dfs(node, visited, recursion_stack, adj):
# Mark the current node as visited and part of the recursion stack
visited[node] = True
recursion_stack[node] = True
# Recur for all the neighbors
for neighbor in adj[node]:
# If neighbor is not visited, then recurse on it
if not visited[neighbor]:
if dfs(neighbor, visited, recursion_stack, adj):
return True
# If the neighbor is in the recursion stack, then a cycle is present
elif recursion_stack[neighbor]:
return True
# The node is removed from the recursion stack before the function returns
# This is the backtracking step
recursion_stack[node] = False
return False
You can also find this implementation and further explanation in the GeeksForGeeks article "Detect Cycle in a Directed Graph".
Application: LeetCode 207 "Course Schedule"
This algorithm isn't just theoretical; it's the direct solution to one of the most common graph interview questions. The problem asks if you can finish all courses given a set of prerequisites. For example, to take course 1, you must first take course 0 ([1, 0]).
This is a directed graph problem where courses are nodes and prerequisites are edges. Can you finish all courses? Yes, if and only if the graph has no cycles. A cycle like 0 -> 1 -> 0 means you can never satisfy the prerequisites.
Course Schedule - Graph Adjacency List - Leetcode 207
The NeetCode video for 'Course Schedule' provides a concise explanation of how to apply our cycle detection algorithm to solve this exact problem. He uses a visitSet, which is functionally identical to our recursion_stack.
Watch from 01:50 to 02:47 to understand the problem setup, and then from 09:10 to 14:37 for the explanation of the DFS algorithm and the code walkthrough. This will solidify the connection between the abstract algorithm and a concrete, high-ROI interview problem.
Test your understanding!
Consider a graph with 4 courses and the following prerequisites: [[1,0], [2,1], [3,2], [1,3]]. This corresponds to the edges: 0->1, 1->2, 2->3, and 3->1.
Trace the dfs algorithm starting from node 0. At what point is the cycle detected? Which arrays (visited and recursion_stack) hold what values at that moment?
Show answer
dfs(0)is called.visited[0]andrecursion_stack[0]becomeTrue.- From
0,dfs(1)is called.visited[1]andrecursion_stack[1]becomeTrue. - From
1,dfs(2)is called.visited[2]andrecursion_stack[2]becomeTrue. - From
2,dfs(3)is called.visited[3]andrecursion_stack[3]becomeTrue. - From
3, we look at its neighbor,1. - The algorithm checks neighbor
1. It seesvisited[1]isTrue. Theelifcondition is checked:recursion_stack[1]is alsoTrue. - Cycle detected! The path
1 -> 2 -> 3 -> 1forms a cycle. Thedfs(3)call returnsTrue, which propagates all the way up.
At this moment, visited is [T, T, T, T] and recursion_stack is [T, T, T, T].
4. Complexity Analysis
For any algorithm, analyzing its performance is key.
- Time Complexity: . This is the standard time complexity for a DFS traversal on a graph represented by an adjacency list. We visit each vertex (
V) and each edge (E) at most once across all DFS calls. - Space Complexity: .
- The
visitedandrecursion_stackarrays each take space. - The depth of the recursion call stack can be at most in the worst case (a long chain).
- Therefore, the total space is proportional to the number of vertices.
- The
This analysis is also covered in the "take U forward" video from 15:30 to 16:15.
Conclusion
You've now learned the standard and efficient method for detecting cycles in directed graphs. This is a significant step up from the undirected case and a powerful pattern to have in your problem-solving toolkit.
Key Takeaways:
- Detecting cycles in directed graphs requires distinguishing between back edges (which form cycles) and cross edges.
- The standard approach uses a two-state system: one to track all
visitednodes and another to track therecursion_stack(nodes in the current DFS path). - A cycle is found when DFS encounters a neighbor that is already present in the
recursion_stack. - This algorithm runs in time and space and is the direct solution to popular interview questions like "Course Schedule."
Preview of the Next Lesson:
The concept of a cycle is fundamental to our next topic: Topological Sorting. A topological sort is a linear ordering of vertices such that for every directed edge u -> v, vertex u comes before vertex v in the ordering. Such an ordering is only possible if the graph is a Directed Acyclic Graph (DAG). In our next lesson, we will build upon today's work to implement algorithms that produce this ordering.