Graphs: BFS & DFS
A graph is nodes plus edges connecting them — no hierarchy required, cycles allowed. Social networks, road maps, the internet, package dependencies, game maps: when things connect to things, it's a graph.
Graphs generalize trees: any node can connect to any other, edges can be one-way (directed) or two-way (undirected), and loops are allowed. The standard representation is an adjacency list — a hash map from each node to the list of its neighbors. Nearly every graph question reduces to one operation: traversal, visiting nodes by following edges. Two orders matter: breadth-first (BFS) and depth-first (DFS).
BFS explores in rings: all direct neighbors first, then neighbors-of-neighbors, and so on outward. It uses a queue (FIFO — the stacks & queues lesson) and a visited set to avoid going in circles. Because it expands one ring at a time, the first time BFS reaches a node is via a shortest path — which is why GPS-style 'fewest hops' problems are BFS.
DFS dives instead: follow one path as deep as it goes, back up, try the next. Swap the queue for a stack — or just use recursion, since the call stack is the stack. DFS answers 'is there any path?', finds connected components, and detects cycles (how npm and pip catch circular dependencies). BFS finds shortest; DFS goes deep. Choosing between them is usually the whole problem.
Both traversals are O(V + E) — every node and edge visited once. The 'visited' set is not optional: without it, any cycle loops forever. Forgetting it is the single most common graph bug. Weighted shortest paths (roads with distances, not hops) need Dijkstra's algorithm — BFS with a priority queue instead of a plain queue.