Hello! Welcome to the final lesson in our module on Graph Traversal Algorithms.
In our previous lessons, we've used traversals like DFS and BFS to explore graph structures, detect cycles, and find connected components. You'll recall that for finding connected components, we used a "master loop" to ensure we visited every node, even in a disconnected graph. Today, we'll build on these ideas to tackle a fundamental problem for a specific type of graph.
Your learning goal for this lesson is to perform a topological sort on a Directed Acyclic Graph (DAG). This is a powerful algorithm with many real-world applications, from scheduling tasks with dependencies to resolving dependencies in software build systems.
1. What is Topological Sorting?
Imagine you have a list of university courses to take, but some have prerequisites. For example, you must take "Intro to Programming" before "Data Structures." A topological sort would give you a valid sequence in which you can take all the courses.
Formally, a topological sort is a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge from vertex u to vertex v, vertex u comes before vertex v in the ordering.

The two key requirements are:
- Directed Graph: The relationships have a one-way dependency (e.g.,
Ais required forB, not the other way around). - Acyclic Graph (No Cycles): There are no circular dependencies. If "Course A" requires "Course B", and "Course B" requires "Course A", it's impossible to create a valid schedule.
A topological sort visually "unravels" the graph into a straight line, where all arrows point from left to right.

To understand these prerequisites more deeply, let's watch a short, clear explanation.
G-22. Kahn's Algorithm | Topological Sort Algorithm | BFS
The video 'Kahn's Algorithm | Topological Sort Algorithm | BFS' from the take U forward channel provides an excellent introduction to what topological sorting is and why it's only valid for Directed Acyclic Graphs (DAGs).
Watch from the beginning until 03:58. Focus on the definition of a linear ordering and the explanations for why topological sort cannot be applied to undirected graphs or graphs with cycles.
There are two primary algorithms to perform a topological sort. We will cover both:
- Kahn's Algorithm, which uses Breadth-First Search (BFS).
- A modified Depth-First Search (DFS) approach.
2. Kahn's Algorithm (BFS-based Approach)
Kahn's algorithm is intuitive because it mimics how you might naturally schedule tasks. You start with the tasks that have no prerequisites. Once you complete them, you can then move on to the tasks that only depended on the ones you just finished.
The key concept here is in-degree: the number of incoming edges a node has.
- A node with an in-degree of 0 has no prerequisites and can be performed first.
The algorithm works as follows:
- Compute In-degrees: Calculate the in-degree for every vertex in the graph.
- Initialize Queue: Add all vertices with an in-degree of 0 to a queue. These are our starting points.
- Process Nodes: While the queue is not empty:
a. Dequeue a vertexu. Add it to your result list (this is the next task in our sorted order).
b. For each neighborvofu, decrement its in-degree by 1 (since we've just "completed"u).
c. If a neighborv's in-degree becomes 0, it means all its prerequisites are met. Addvto the queue. - Check for Cycles: After the loop, if the number of vertices in your result list is less than the total number of vertices in the graph, it means there was a cycle. A cycle prevents the in-degrees of some nodes from ever reaching 0.
Let's see this process in action.
G-22. Kahn's Algorithm | Topological Sort Algorithm | BFS
The same video from take U forward provides a fantastic step-by-step walkthrough of Kahn's algorithm, showing how the in-degrees and the queue change over time.
Watch from 03:58 to 10:31. Pay close attention to how the in-degree array is calculated and updated, and how nodes are added to the queue only when their in-degree drops to zero.
Python Implementation of Kahn's Algorithm
Now, let's look at a clean Python implementation. This is a common pattern you'll use in competitive programming.
Topological Sort (With Visualization and Code Examples)
The article 'Topological Sort (With Visualization and Code Examples)' from FinalRoundAI provides a great Python implementation for Kahn's Algorithm. We'll focus on this code.
In the article, find the section titled 'Brute Force Approach' (don't worry about the name, it's referring to the standard Kahn's algorithm). Study the 'Code (Python)' provided there. Notice the use of defaultdict for the adjacency list and deque for the queue, tools we've covered previously.
Here is the code from the article, with added comments to highlight the steps:
from collections import deque, defaultdict
def topological_sort_kahns(vertices, edges):
# Step 1: Build adjacency list and calculate in-degrees
graph = defaultdict(list)
in_degree = {i: 0 for i in range(vertices)}
for src, dest in edges:
graph[src].append(dest)
in_degree[dest] += 1
# Step 2: Find all vertices with no incoming edges (in-degree == 0)
queue = deque()
for vertex in range(vertices):
if in_degree[vertex] == 0:
queue.append(vertex)
result = []
# Step 3: Process vertices from the queue
while queue:
current = queue.popleft()
result.append(current)
# For each neighbor, reduce its in-degree
for neighbor in graph[current]:
in_degree[neighbor] -= 1
# If in-degree becomes 0, add to queue
if in_degree[neighbor] == 0:
queue.append(neighbor)
# Step 4: Check if topological sorting is possible (no cycle)
if len(result) != vertices:
return [] # Graph has a cycle
return result
# Example usage
vertices = 6
edges = [(5, 2), (5, 0), (4, 0), (4, 1), (2, 3), (3, 1)]
print("Topological Sort (Kahn's):", topological_sort_kahns(vertices, edges))
# Possible output: [4, 5, 0, 2, 3, 1] or [5, 4, 2, 0, 3, 1] etc.
Test your understanding!
Consider a graph with 4 vertices (0, 1, 2, 3) and edges [(0, 1), (0, 2), (1, 3), (2, 3)]. Trace Kahn's algorithm.
- What are the initial in-degrees of each node?
- Which node(s) get added to the queue initially?
- What is one possible final topological sort?
Show answer
- Initial in-degrees:
- Node 0: 0
- Node 1: 1 (from 0)
- Node 2: 1 (from 0)
- Node 3: 2 (from 1 and 2)
- Initial queue: Only node 0 has an in-degree of 0, so the queue starts with
[0]. - Trace & Final Sort:
pop(0).result = [0]. Neighbors of 0 are 1 and 2.in_degree[1]becomes 0. Add 1 to queue.queue = [1].in_degree[2]becomes 0. Add 2 to queue.queue = [1, 2].pop(1).result = [0, 1]. Neighbor of 1 is 3.in_degree[3]becomes 1. Not added to queue yet.queue = [2].pop(2).result = [0, 1, 2]. Neighbor of 2 is 3.in_degree[3]becomes 0. Add 3 to queue.queue = [3].pop(3).result = [0, 1, 2, 3]. No neighbors. Queue is empty.- A possible final sort is
[0, 1, 2, 3].
3. DFS-based Approach
An alternative method uses Depth-First Search. This approach can feel a bit less direct, but it's very powerful and a natural extension of your DFS skills.
The core idea is based on post-order traversal. A node is only considered "finished" after all of its descendants in the DFS tree have been visited. In a topological sort, a node must appear before its descendants. This means if we add a node to our result list as soon as it's finished, we are building the topological sort in reverse order.
The algorithm works as follows:
- Initialize a
visitedset and a list/stack to store the result. - Iterate through all vertices. If a vertex hasn't been visited, start a DFS from it.
- In the
dfs(vertex)function:
a. Mark thevertexas visited.
b. For each neighbor of thevertex, if it hasn't been visited, recursively calldfson it.
c. After the loop over neighbors finishes (this is the post-order step), add the currentvertexto the front of the result list (or append to a list and reverse it at the end). - Cycle Detection: To detect a cycle, we need an extra
recursion_stackset. Before visiting a node's neighbors, add it to therecursion_stack. After the visit, remove it. If you ever try to visit a node that is already in therecursion_stack, you've found a back edge, which indicates a cycle.
This DFS approach is often used in more complex problems. Let's watch an explanation of this logic in the context of a challenging competitive programming problem.
Alien Dictionary - Topological Sort - Leetcode 269 - Python
The video 'Alien Dictionary - Topological Sort' by NeetCode explains the post-order DFS logic very clearly. While the problem itself is hard, the core explanation of the algorithm is excellent.
Watch from 12:15 to 15:16. The first part (12:15-14:03) explains why a post-order DFS builds the result in reverse. The second part (14:03-15:16) explains how to use an extra set to detect cycles during the traversal.
Python Implementation of DFS-based Topological Sort
Now, let's examine the Python code for this approach.
Topological Sort (With Visualization and Code Examples)
The same FinalRoundAI article also provides an excellent implementation for the DFS-based topological sort.
Find the section 'Optimized Approach' in the article and study the Python code provided there. Pay close attention to the three states tracked for each node: not visited, in recursion stack (rec_stack), and fully visited.
Here's the code, again with comments to guide you:
def topological_sort_dfs(vertices, edges):
# Step 1: Build the adjacency list
graph = {i: [] for i in range(vertices)}
for src, dest in edges:
graph[src].append(dest)
visited = [False] * vertices
rec_stack = [False] * vertices # For cycle detection
result_stack = []
def dfs(vertex):
# Mark current vertex as visited and add to recursion stack
visited[vertex] = True
rec_stack[vertex] = True
# Visit all neighbors
for neighbor in graph[vertex]:
# If neighbor is in recursion stack, we have a cycle
if rec_stack[neighbor]:
return False # Cycle detected
# If neighbor is not visited, recursively visit it
if not visited[neighbor]:
if not dfs(neighbor):
return False # Propagate cycle detection
# After visiting all descendants, remove from recursion stack
rec_stack[vertex] = False
# Post-order step: add vertex to the result
result_stack.append(vertex)
return True
# Perform DFS for all unvisited vertices (handles disconnected graphs)
for vertex in range(vertices):
if not visited[vertex]:
if not dfs(vertex):
return [] # Cycle detected, no topological sort possible
# The stack was built in post-order, so reverse it for the final result
return result_stack[::-1]
# Example usage
vertices = 6
edges = [(5, 2), (5, 0), (4, 0), (4, 1), (2, 3), (3, 1)]
print("Topological Sort (DFS):", topological_sort_dfs(vertices, edges))
# Possible output: [5, 4, 2, 3, 1, 0] or [4, 5, 2, 3, 1, 0] etc.
Conclusion
You have now mastered topological sorting, a key algorithm for directed graphs. You've learned not just what it is, but two distinct and equally valid ways to implement it.
Key Takeaways:
- A topological sort is a linear ordering of vertices in a DAG where for every edge
u -> v,uappears beforev. - It's essential for problems involving dependencies, such as task scheduling or build systems.
- Kahn's Algorithm uses a BFS-like approach, processing nodes with an in-degree of 0.
- The DFS-based algorithm uses a post-order traversal to build the sorted list in reverse.
- Both algorithms have a time complexity of and can be adapted to detect cycles. If a cycle is present, a topological sort is not possible.
Up Next:
This lesson concludes our module on graph traversals. You've built a strong foundation. In our next module, "Shortest Paths and Minimum Spanning Trees," we will start by exploring Dijkstra's algorithm. This will show you how to find the shortest path from a single source to all other nodes in a weighted graph, and it will be a great opportunity to apply your knowledge of heaps and priority queues