03 — Graphs, Cycle Detection & DAG Task Scheduling
At a glance
After completing this lesson you can:
- Represent directed and undirected graphs via adjacency lists.
- Detect deadlocks, dependency cycles, and infinite loops using 3-color DFS.
- Compute topological execution orders for task workflows (Airflow/Celery/Temporal DAGs) using Kahn's algorithm (indegree BFS).
Time: ~50 minutes · Prerequisites: 02 — Binary Search & Predicates
Reading Companion
- Book: Grokking Algorithms (2nd Edition) — Aditya Y. Bhargava
- Chapter 6: Breadth-First Search (Shortest Path, Queue traversal).
- Chapter 7: Trees and Graphs (Graph modeling, directed vs undirected).
Why this matters in production
Workflow engines (Temporal, Celery Canvas, Airflow) schedule jobs based on Directed Acyclic Graphs (DAGs). If a developer creates a circular task dependency (Job A depends on Job B, which depends on Job A), a naive scheduler will hang forever or crash when stack overflow occurs. Running topological sorting and cycle detection upfront guarantees that tasks run in valid dependency order with guaranteed termination.
Deep dive
1. Adjacency List Representation
Graphs are represented in Python as dictionaries of sets/lists:
graph = {
"extract": ["transform"],
"transform": ["validate", "enrich"],
"validate": ["load"],
"enrich": ["load"],
"load": []
}
2. Kahn's Algorithm (Topological Sort via In-degrees)
- Calculate the in-degree (number of incoming edges/dependencies) for each node.
- Initialize a queue with all nodes having
in_degree == 0. - While queue is not empty: - Pop node $u$, add to topological order. - For each neighbor $v$ of $u$, decrement $v$'s in-degree. - If $v$'s in-degree becomes $0$, enqueue $v$.
- If topological order length $
e$ total node count, a cycle exists!
Hands on
python3 phase1-cs-and-architecture/02-algorithmic-patterns/03-graphs-bfs-dfs-and-toposort/examples/01_demo.py
python3 phase1-cs-and-architecture/02-algorithmic-patterns/03-graphs-bfs-dfs-and-toposort/examples/02_trap.py
AI Trap & Code Review
# BAD AI-GENERATED CODE — RECURSIVE DFS WITHOUT CYCLE DETECTION
def run_dag(node, graph, visited):
visited.append(node)
for neighbor in graph.get(node, []):
run_dag(neighbor, graph, visited) # Infinite recursion on cycle!
Exercises
- Rung 1 (
compute_indegrees): Calculate the in-degree of every node in a directed graphdict[str, list[str]]. - Rung 2 (
detect_cycle_directed— Fix the AI Bug): Fix an infinite recursion bug in dependency resolution using 3-color DFS (WHITE,GRAY,BLACK) to detect cycles. - Rung 3 (
topological_sort_kahn): Implement Kahn's algorithm returning a valid execution order or raisingValueError("Cycle detected in DAG"). - Rung 4 (
shortest_dependency_path): Use BFS to find the shortest dependency chain between two services in a service-to-service call graph. - Rung 5 (
parallel_dag_scheduler): Given a DAG of tasks, compute the minimum number of parallel execution stages where independent tasks run concurrently.
Wrapping up
Can you explain?
- Why does Kahn's algorithm detect circular dependencies automatically?
- What do the 3 colors (
WHITE,GRAY,BLACK) represent during cycle detection in DFS? - What is the difference between BFS and DFS in graph exploration?
Further Reading & References
- CPython Source: listobject.c (Dynamic Array Implementation)
- Raymond Hettinger: Modern Dictionaries by Key-Sharing
- Alex Xu: System Design Interview — Rate Limiter Design
- Tim Roughgarden: Algorithms Illuminated Series
Show reference solution notes
Solution & Engineering Notes — 03 Graphs & Topological Scheduling
- Diamond DAGs vs Cycles: A diamond DAG (
A -> B -> D,A -> C -> D) is NOT a cycle. Naive visited sets flagDtwice and report false positive cycles. The 3-color DFS correctly distinguishes cross-edges from back-edges (GRAYnodes currently on the call stack). - Kahn's BFS vs DFS for scheduling: Kahn's algorithm naturally yields parallel execution stages: each batch of zero in-degree nodes can run concurrently on background workers.
Self-check
Run python3 -m unittest phase1-cs-and-architecture/02-algorithmic-patterns/03-graphs-bfs-dfs-and-toposort/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.
Completion edits PROGRESS.md; commit it with the lesson.