Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to our module on Graph Algorithms.
In the last two lessons, you implemented topological sort using two distinct methods: Kahn's algorithm (an iterative, BFS-based approach) and a recursive, DFS-based approach. You now have two working tools that solve the same problem.
This brings us to a critical question for any software engineer: which one should you choose? The goal of this lesson is to move beyond implementation and into analysis. We will compare the trade-offs between Kahn's algorithm and the DFS approach, including for parallel execution. Understanding these differences is essential for designing robust and efficient systems, especially in high-performance environments like the ones you've worked in at AWS.
1. Foundational Trade-offs: A Side-by-Side Look
At their core, the two algorithms approach the problem from different philosophical starting points. DFS explores as deeply as possible before backtracking, while Kahn's algorithm processes nodes in layers based on their dependencies. This fundamental difference leads to several important trade-offs.
To get a structured overview, let's start with a resource that provides a concise comparison.
Topological Sorting in DAGs: Algorithms & Practical Guide - upGrad
The article 'Topological Sorting in DAGs' from upGrad offers a clear table comparing the DFS and BFS (Kahn's) approaches. It also discusses some of the general challenges of topological sorting.
Please read the section titled 'DFS vs. BFS for Topological Sorting', focusing on the comparison table. Then, briefly review the tables in 'Key Challenges and Limitations' and 'Advantages and Disadvantages'. These will frame our discussion.
The resource you just read provides a high-level summary. Let's break down the most critical points with more engineering detail:
| Feature | DFS-Based Approach | Kahn's Algorithm (BFS-Based) |
|---|---|---|
| Structure | Recursive. Naturally maps to problems defined by call stacks (e.g., function call resolution). Can be elegant and concise. | Iterative. Uses a queue and explicit in-degree counts. Avoids recursion limits and is often more intuitive for scheduling systems. |
| Output | Produces one of many valid topological orders. The specific order depends on the traversal path, which can seem arbitrary. | Produces a "layered" or "leveled" ordering. All nodes with 0 initial dependencies are first, then all nodes that become free, etc. |
| Cycle Detection | Intrinsic. A cycle is detected during traversal if we encounter a node currently in the recursion stack (a "visiting" state). This provides an immediate failure signal. | Extrinsic. A cycle is inferred after the algorithm runs, if the number of sorted nodes is less than the total number of nodes in the graph. |
| Memory Usage | Uses the system's recursion stack, which has a space complexity of in the worst case (a long, chain-like graph). | Uses an explicit queue and an in-degree array. Both have a space complexity of . The practical overhead may be lower than a deep recursion stack. |
A key takeaway is that neither algorithm is universally "better." The choice depends on the specific constraints of your problem.

2. The Critical Differentiator: Parallel Execution
For many applications, the performance of a sequential sort is sufficient. However, in systems that schedule thousands of concurrent tasks—similar in spirit to the inference scheduler you built—parallelism becomes a key concern. This is where the two algorithms diverge significantly.
Parallelizing Kahn's Algorithm
Kahn's algorithm is naturally suited for parallel execution. The core insight is that all nodes with an in-degree of zero can be processed concurrently.
The parallel workflow looks like this:
- Initialization: Calculate all initial in-degrees. Populate a shared, thread-safe work queue with all nodes where in-degree is 0.
- Concurrent Processing: Multiple worker threads pull nodes from the queue and process them in parallel.
- Synchronization: When a worker finishes processing node , it iterates through 's neighbors. For each neighbor , it atomically decrements 's in-degree count.
- Enqueueing New Work: If the atomic decrement causes a neighbor's in-degree to become 0, that worker adds the neighbor to the shared work queue.
This model is a classic producer-consumer pattern, where workers consume tasks from a queue and produce new ones as dependencies are met. Your experience with MPSC channels in Rust for managing concurrent tasks is directly analogous to the thread-safe work queue needed here.
The Difficulty of Parallelizing DFS
The DFS-based approach is fundamentally sequential. The algorithm's correctness relies on the post-order completion of recursive calls. You cannot finish processing a node until the entire DFS subtree for each of its children has been fully explored. This inherent dependency makes it extremely difficult to break the problem down for parallel workers within a single connected component of the graph.
3. A Look at Performance in Practice
Theoretical advantages are one thing, but real-world performance involves overheads. The following research paper, while a bit academic, provides an excellent case study on implementing a parallel topological sort based on Kahn's algorithm and comparing it to a sequential DFS.
[PDF] Implementing Parallel Topological Sort in a Java Graph Library
This paper, 'Implementing Parallel Topological Sort in a Java Graph Library', details the transformation of Kahn's algorithm into a parallel version and benchmarks it against the sequential DFS algorithm. The discussion of the results is particularly relevant.
First, read section 7.1, 'The algorithm' (page 5), to see how the sequential Kahn's algorithm is adapted for a multi-threaded environment. Note the problem of race conditions and the proposed solution using an atomic boolean. Next, and most importantly, read section 8.3, 'Discussion' (page 6). This section analyzes why their parallel implementation was often slower than the sequential DFS and under what conditions it might win. Focus on the factors they identify: Overhead, Shared resources, and Low task parallelism.
The findings from that paper are crucial for any engineer implementing such a system:
- Parallelism has a cost: The overhead of managing threads, synchronizing access to shared data (like the in-degree counts and the work queue), and the termination detection logic can outweigh the benefits of parallel processing, especially for smaller or less complex graphs.
- Shared resources create bottlenecks: The work queue and the result list are critical sections. If many threads are trying to access them simultaneously, they will spend time waiting on locks instead of doing useful work. This is a classic challenge in concurrent system design.
- Task availability matters: The effectiveness of parallel Kahn's depends on having a sufficient number of nodes with an in-degree of 0 available in the queue at any given time. If the graph is very "narrow" (e.g., a long chain), there may only be one task available at a time, rendering multiple workers useless.
Despite these challenges, the paper concludes that for a large enough and complex enough graph, the parallel approach is expected to eventually outperform the sequential one. For a system processing a massive dependency graph of inference jobs, this scaling property is exactly what you'd be looking for.
Conclusion: Making the Right Choice
You now have a multi-faceted view for comparing these two algorithms. Let's summarize the decision-making process.
Key Takeaways:
- DFS-based sort is simple, recursive, and excellent for situations where you just need any valid topological order quickly and parallelism is not a concern. Its built-in cycle detection is elegant.
- Kahn's algorithm is iterative, queue-based, and its structure is a natural fit for task schedulers. Its key advantage is parallelizability, making it the superior choice for high-throughput systems that operate on large, complex dependency graphs, despite the implementation overhead.
- The performance of a parallel algorithm is not guaranteed to be better. You must consider the overhead of synchronization and the inherent parallelism of the graph itself (i.e., its "width").
For an interview at a company like Anthropic, being able to articulate these trade-offs—especially the nuances of parallelization, synchronization costs, and scalability—demonstrates a deep level of engineering maturity that goes far beyond simply knowing the base algorithms.
Next Lesson Preview
In both of our topological sort implementations, we had to handle cycles. A topological sort is only possible on a Directed Acyclic Graph (DAG), and our systems must fail gracefully if a cycle is present. In our next lesson, we will focus specifically on this prerequisite, dedicating the entire session to implementing a robust cycle detection algorithm for a directed graph.