Graphs, BFS & DFS
- Represent a graph as an adjacency list and say when a matrix wins instead
- Implement BFS with a queue and use it for shortest unweighted paths
- Implement DFS both recursively and with an explicit stack
- Detect cycles and count connected components
- Explain topological sort and where it applies (DAGs only)
| Spaced-rep warm-up: due cards (heaps, trees, queues) | 10 min |
| ELI5 + tech read; run BFS in the visualizer and watch the rings | 18 min |
| Guided: BFS shortest path, islands, cycle detection | 42 min |
| Practice: rotting oranges (multi-source BFS) | 20 min |
| Project: graph toolkit + import-dependency mapper | 20 min |
| Quiz + flashcards | 10 min |
Builds on: Day 25 โ Stacks & queues ยท Day 27 โ Recursion & divide/conquer ยท Day 29 โ Binary trees & BSTs
A friendship map: dots for people, lines for "knows each other." No root, no up or down, no rule about two children โ just things and connections. That is a graph, and it is the most general shape data takes: cities and roads, packages and dependencies, web pages and links, tasks and prerequisites.
Two ways to explore it. BFS is a ripple: drop a stone at your starting person and the wave reaches all their friends first, then friends-of-friends, then friends-of-friends-of-friends. Because the ripple expands one ring at a time, the first time it touches someone is provably the SHORTEST path to them โ that is BFS's superpower. DFS is exploring corridors: pick a hallway, follow it to the very end, back up to the last junction, try the next hallway. You do not find shortest paths this way, but you thoroughly map every reachable room โ perfect for "is there any path?", "does this maze loop back on itself?", and "which rooms form one connected building?" One structure decides everything: a queue makes the ripple, a stack (or recursion โ the call stack) makes the corridor-crawl.
Graphs are the interview category that separates prepared candidates, and they saturate real AI work: dependency resolution in build systems and package managers is topological sort; a multi-step agent's tool-call plan (Day 121) is a DAG; knowledge-graph RAG (Day 118) retrieves by walking edges; crawl-and-ingest pipelines for RAG corpora are literally BFS over hyperlinks. When your Day 148 Docker builds order their layers or your Day 152 CI pipeline orders its jobs, a topological sort ran. "Model the mess as nodes and edges, then BFS/DFS it" is a career-long move.
The ripple โ BFS explores in rings from A
step 1 / 6A friendship map. BFS explores like a ripple: everyone 1 hop away, THEN everyone 2 hops away. A queue enforces the order โ first discovered, first explored.queue: [A]
Guided practice
Adjacency list + BFS shortest path
20 min- Create
graphs_lab.py. Build the starter's small undirected graph as a dict-of-sets adjacency list. - Implement
bfs_distances(graph, start)returning a dict node โ distance. Discipline: mark visited when ENQUEUEING. Predict the distances from "a" on paper first. - Extend it to
shortest_path(graph, start, goal)by also recording each node's parent when discovered, then walking parents backwards from the goal. Verify the path found for a โ f is one of the shortest ones. - Break it on purpose: move the visited-marking to pop time on a graph with a cycle and print the queue's length over time. Watch the duplicate explosion โ now you will never make that mistake under interview pressure.
Number of Islands + cycle detection, think-aloud
22 min- Classic 1 โ Number of islands. Think aloud: "The grid IS a graph โ cells are nodes, 4-neighbors are edges. Each island is a connected component. Scan every cell; when I hit unvisited land, that is a NEW island: count it and flood-fill (DFS) the whole component so I never count it again." Implement
count_islandswith the starter grid; use an explicit stack so a huge grid cannot overflow recursion. - Trace your flood fill on the 3-island starter grid by hand for the first island โ list the cells in the order visited.
- Classic 2 โ Course Schedule. Think aloud: "Courses and prerequisites form a directed graph. I can finish all courses if and only if there is no cycle." Implement
can_finishwith DFS three-state coloring: unvisited / on-current-path / done. Revisiting an on-path node = cycle. - Test with a cyclic prerequisite pair (A needs B, B needs A) and a valid chain. Then say the connection: three-state DFS and Kahn's algorithm are two roads to the same theorem โ topological order exists iff no cycle.
On your own
Rotting oranges (multi-source BFS)
20 minA grid holds fresh oranges (1), rotten oranges (2), and empty cells (0). Every minute, rot spreads to 4-adjacent fresh oranges. Return the minutes until no fresh orange remains, or -1 if some orange can never rot.
Solve with BFS. The twist to discover: the ripple starts from ALL rotten oranges at once. Test on a grid where two rot sources race toward the same fresh orange, and on a grid with an unreachable orange.
Hints: seed the queue with every rotten cell (all at distance 0) before looping โ multi-source BFS is ordinary BFS with a bigger starting ring. Count remaining fresh oranges to detect the -1 case.
Graph toolkit + the import-dependency mapper
Two deliverables in dsa/graphs.py. First: today's core โ bfs_distances, shortest_path, count_islands, can_finish โ with pytest cases including a disconnected graph and a self-loop. Second, the fun one: import_graph(directory) walks your practice repo's Python files (pathlib from Day 5), regex-extracts local import statements (Day 13), builds a directed graph of module dependencies, and prints a topological order โ or names the cycle if one exists. Run it on your own repo and commit the output in the docstring.
Common mistakes & misconceptions
- Marking nodes visited at POP time instead of enqueue time โ the queue floods with duplicates and BFS silently goes superlinear. Mark when enqueueing.
- Using DFS for shortest path. DFS finds A path, not the shortest; only BFS guarantees minimal edges in unweighted graphs.
- Forgetting the visited set entirely on cyclic graphs โ infinite loop. Trees forgive this (no cycles); graphs never do.
- Recursion-depth crashes on big grids: a 1000ร1000 grid can recurse a million deep. Know the iterative-stack DFS.
- Detecting directed cycles with a plain visited set. A visited node reached again via a DIFFERENT path is not a cycle โ you need the "on current path" state.
- Trying to topologically sort a cyclic graph. Topo order exists only for DAGs; the algorithm failing IS your cycle detector.
Q1. Why does BFS โ and not DFS โ find shortest paths in unweighted graphs?
Q2. In BFS, when should a node be marked visited?
Q3. Course A requires B, B requires C, C requires A. What does topological sort do here?
Go deeper โ curated resources
- Dijkstra = BFS + a priority queue โ Swap the BFS queue for Day 30's min-heap keyed by path cost and you get Dijkstra's algorithm for weighted graphs. Sketch the change on paper โ it is a 5-line diff.
- BFS marks visited at enqueue; duplicate-explosion experiment observed and explained
- Number of islands and course schedule pass tests including edge cases
- Rotting oranges handles the unreachable-orange case (-1)
- Import-dependency mapper runs on your real repo; toolkit committed
- Quiz โฅ 2/3
โ Back: BFS is Day 25's queue plus Day 29's level-order, generalized; DFS is Day 27's recursion with a visited set because graphs, unlike trees, can loop. The adjacency list is Day 23's dict-of-sets.
Forward โ: Day 34's DP problems are often shortest paths on implicit DAGs. Day 118's graph-RAG retrieves by edge-walking, Day 121's agent plans are DAGs your topo-sort intuition will read, and Day 148's Docker layer graph and Day 152's CI job ordering are topological sorts in production clothing.