Recommended resources
The freely available resources this lesson will direct you to.
Welcome to our next lesson on graph algorithms. Last time, we took a deep dive into Kahn's algorithm, an intuitive, BFS-based approach to topological sorting that mirrors how you might schedule tasks by repeatedly picking those with no outstanding prerequisites.
Today, we'll explore the second major strategy for this problem: a method built around Depth-First Search (DFS). This approach leverages the natural structure of recursion to determine the ordering. You'll learn how the post-order traversal property of DFS is the key to this algorithm and how to implement it, including the critical logic for detecting cycles. Understanding both the BFS and DFS patterns for topological sort will give you a versatile toolkit for dependency-related problems in coding interviews.
By the end of this lesson, you will be able to implement a complete topological sort using a DFS-based, post-order traversal approach.
The Core Idea: Finishing Order is Key
In our last lesson, we built the topological sort by finding nodes with an in-degree of 0 and adding them to our list. The DFS approach works from the opposite end. It explores as far down a dependency chain as possible before adding anything to the list.
Think of a recursive DFS traversal on a graph. A function call for a node u will only finish and return after all of its descendants (the nodes it has edges to) have been fully explored. This moment—right before the function for u returns—is called the post-order position.
This property is exactly what we need for topological sort. A task can only be considered "complete" after all of its prerequisites are complete. In DFS terms, we only "finish" with a node u after we have finished with all the nodes v that u points to.
Therefore, the algorithm is:
- Perform a DFS traversal on the graph.
- When a node is "finished" (i.e., all its neighbors have been visited, and the recursion is about to backtrack from it), add it to a list. This list represents the post-order traversal.
- The final topological sort is simply the reverse of this post-order list.
A node that is finished last has no other nodes depending on it (in its DFS subtree), making it a candidate for the end of a topological sort. Conversely, a node that depends on many others will only be finished after all its dependencies are finished, placing it earlier in the post-order list and thus later in the final reversed (topological) order.
The following article from Labuladong's notes provides an excellent intuition for this, using a binary tree as a simple analogy before applying it to a general graph.
Cycle Detection and Topological Sort Algorithm
This section explains the core intuition behind the DFS-based approach.
Read the section explaining the logic. Focus on the connection between a node's dependencies and the post-order traversal property, where a node is processed only after its children (or dependencies) are fully traversed.
Visualizing the DFS Approach
Let's make this more concrete. The key steps are traversing deep, and then adding nodes to a list as we backtrack.
The image below shows a simple graph. A DFS traversal might start at node 0, go to 1, then 4. Node 4 has a neighbor 7, so we go there. Node 7 has no outgoing edges, so its DFS call finishes. We add 7 to our post-order list. We backtrack to 4, which has no more neighbors to visit. We add 4 to the list. This continues until the entire graph is traversed.

For an animated walkthrough, the following video by WilliamFiset is very helpful. It shows the call stack growing and shrinking, and how nodes are added to the ordering on the "recursive callback."
Topological Sort Algorithm | Graph Theory
This video, "Topological Sort Algorithm," demonstrates the DFS approach.
First, watch the brief introduction to the algorithm from here. Then, follow the animated example from this section. Observe how the algorithm picks a starting node, explores deeply, and adds nodes to the topological ordering as it backtracks up the call stack.
Implementation: DFS with Cycle Detection
A critical part of any topological sort algorithm is handling cycles. A topological sort is only defined for a Directed Acyclic Graph (DAG). If a cycle exists, it's impossible to create a valid ordering.
In a DFS-based approach, we can detect a cycle by keeping track of the nodes currently in our recursion stack. We use a three-state system for each node:
- Unvisited (White): The node has not been explored yet.
- Visiting (Gray): The node is currently in the recursion stack. We have started a DFS from this node, but we have not finished exploring all its descendants.
- Visited (Black): The DFS call for this node and all its descendants has completed.
A cycle is detected if, during our traversal from a node u, we encounter a neighbor v that is already in the "Visiting" (Gray) state. This means we have found a path back to a node that is one of its own ancestors in the current DFS path—a back edge, which defines a cycle.

Now, let's put this all together into a concrete implementation plan, using the classic LeetCode problem "Course Schedule II" as our guide. Your task is to return a valid course order, or an empty list if a cycle exists.
The following video from NeetCode provides a fantastic, practical walkthrough of implementing this in Python, which is directly applicable to interview settings.
Course Schedule II - Topological Sort - Leetcode 210
This video, "Course Schedule II - Topological Sort," details a complete DFS solution for this classic problem.
Watch the initial problem breakdown and the high-level DFS strategy from this segment. Notice how he traces the DFS path and adds courses to the output as he backtracks. Pay close attention to the explanation of cycle detection from here, where he introduces the idea of tracking the current path. Follow the code setup from this part, where he creates the adjacency list and the visit and cycle sets to manage the three node states. The core logic is in the dfs function walkthrough from here. This is the most important part. Understand how the base cases (cycle found, already visited) work and where the node is added to the output (post-order). Finally, see how the main loop ties it all together by calling DFS for every course from the end.
The Algorithm in Pseudocode
Based on the concepts we've discussed, here is a structured summary of the algorithm:
-
Initialization:
- Build an adjacency list for the graph.
- Initialize an empty list for the topological order (
result). - Initialize two sets:
visited(for black nodes) andrecursion_stack(for gray nodes).
-
Main Loop:
- Iterate through each node
ifrom0toN-1. - If
iis not invisited, calldfs(i). - If
dfs(i)returnsFalse(indicating a cycle), stop and return an empty list.
- Iterate through each node
-
DFS Function (
dfs(node)):- Base Case 1 (Cycle): If
nodeis inrecursion_stack, a cycle is detected. ReturnFalse. - Base Case 2 (Already Processed): If
nodeis invisited, it has already been processed as part of another DFS path. ReturnTrue. - Mark as Visiting: Add
nodetorecursion_stack. - Recursive Step: For each
neighborofnode:- If
dfs(neighbor)returnsFalse, propagate the failure by returningFalseimmediately.
- If
- Mark as Visited (Post-order):
- Remove
nodefromrecursion_stack. - Add
nodetovisited. - Append
nodeto theresultlist.
- Remove
- Return
True.
- Base Case 1 (Cycle): If
-
Final Step:
- If the main loop completes without detecting a cycle, the
resultlist contains the post-order traversal. Returnresultreversed.
- If the main loop completes without detecting a cycle, the
This implementation is robust, handling disconnected components and correctly identifying cycles. For a clean, text-based implementation, you can refer back to the Labuladong article.
Cycle Detection and Topological Sort Algorithm
This article provides a full implementation based on the Course Schedule II problem.
Review the solution code. This code integrates the cycle detection (hasCycle, onPath) and post-order recording (postorder.add(s)) into a single traverse function. Notice the final step is to reverse the postorder list.
Complexity Analysis
Just like Kahn's algorithm, the DFS-based approach is very efficient.
-
Time Complexity:
- Each vertex
vis visited exactly once. Thedfs(v)function is called once for each vertex. - When inside
dfs(v), we iterate through all of its outgoing edges. Over the entire algorithm, every edgeeis traversed exactly once. - Therefore, the total time is proportional to the number of vertices and edges.
- Each vertex
-
Space Complexity:
- to store the adjacency list.
- for the
visitedandrecursion_stacksets. - for the recursion stack in the worst case (for a path graph).
Conclusion
In this lesson, we've dissected the DFS-based approach to topological sorting. By leveraging the post-order property of a depth-first search, we can construct a valid task ordering in a way that is both elegant and efficient. You are now equipped with two distinct, powerful patterns for solving problems involving dependencies.
Key Takeaways:
- Core Logic: The reverse of a DFS post-order traversal on a DAG yields a valid topological sort.
- Post-order Intuition: A node is added to the post-order list only after all its dependencies have been fully explored and added.
- Cycle Detection: A three-state system (unvisited, visiting, visited) is used. A cycle is found if we encounter a "visiting" node.
- Implementation: The algorithm is naturally recursive. A main loop ensures all graph components are visited, and a helper DFS function handles traversal, cycle detection, and building the post-order list.
- Comparison: While Kahn's algorithm is iterative (BFS), the DFS approach is recursive. Both have the same time complexity and are valid choices for interview problems.
In our next lesson, we will move beyond just implementing these algorithms. We'll focus on applying them to solve a variety of interview-style problems involving task scheduling, dependency resolution, and finding the shortest/longest path in DAGs.