</> MAANG.io
coding interview ยท 101

Foundations

Master coding interviews with comprehensive coverage of data structures, algorithms, and problem-solving techniques. Progress from fundamentals to advanced topics with expertly curated content.

0/255 solved 0% complete

Graph Traversal: DFS and BFS

What is this?

Traversal is just "visiting every dot in the graph, one at a time, without going in circles." There are two natural styles. DFS (depth-first) is like exploring a maze by always walking forward until you hit a dead end, then backing up โ€” going deep first. BFS (breadth-first) is like ripples spreading from a stone dropped in a pond โ€” you check everything one step away, then everything two steps away. Same map, two ways to walk it, and the choice decides what you can answer.

flowchart TD A["Start at a node"] --> B["Pick a frontier holder"] B --> C["A stack goes deep first DFS"] B --> D["A queue goes wide first BFS"] C --> E["Mark each node visited so you never loop"] D --> E

๐Ÿ’ก Fun fact: breadth-first search was invented twice. Konrad Zuse described it in his 1945 PhD thesis (which went unpublished until 1972), and Edward F. Moore reinvented it in 1959 to find the shortest path out of a maze โ€” which is why BFS is sometimes called the "Moore algorithm."

๐Ÿ”“ The 2 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: almost every "explore this graph" question reduces to one disciplined sweep that carries a visited set plus a frontier. Swap the frontier's data structure and you swap the strategy: a stack/recursion gives DFS (go deep), a queue gives BFS (go wide, shortest hops first). Master that one template and a whole family of graph problems collapses into it.


1. What graph traversal (DFS/BFS) really means

A graph is just nodes plus edges โ€” relationships with no inherent order. Traversal is the act of visiting every reachable node exactly once without getting lost in cycles. Two pieces make that work:

Adjacency list and visited set, the two structures every traversal carries

The only structural difference between the two algorithms is what holds the frontier โ€” the nodes discovered but not yet expanded:

DFS uses a stack, BFS uses a queue

That single swap changes everything downstream: DFS naturally follows long paths and backtracks (great for connectivity, cycles, topological order); BFS fans out in concentric rings, so the first time it reaches a node it did so in the fewest edges โ€” the shortest path in an unweighted graph.

โš ๏ธ The number-one beginner bug is omitting the visited set. In any graph with a cycle (and undirected graphs are cyclic by nature โ€” every edge is a 2-cycle), a traversal with no visited set loops forever.


2. Where you'll actually meet this

DFS/BFS are not interview toys โ€” they are everywhere systems reason about relationships:

  • Distributed systems. Service-dependency walks, partition/reachability checks ("can this replica reach quorum?"), snapshotting a cluster topology, and deadlock detection (cycle-finding) are all graph traversals over the connectivity graph.
  • Networking. Route discovery and link-failure blast-radius are reachability sweeps; BFS underlies shortest-hop routing in unweighted topologies.
  • Compute / runtimes. Garbage collectors traverse the object graph (mark-and-sweep is a DFS/BFS with a visited set); build systems topologically order dependency DAGs with DFS.
  • Retail-ops / finance. Flow reachability through supply lanes or settlement graphs โ€” "can value/goods move from A to B?" is a same-component query.

If a problem says visit all, connected, path exists, shortest steps, component, cycle, copy the graph โ€” this chapter's template applies.


3. The traversal toolkit (reflexes to build)

  1. Always carry a visited set. Mark a node the moment you discover it. This breaks cycles, prevents double work, and guarantees the traversal ends. For object graphs, use a dict/set keyed by the node object.
  2. Build an adjacency list, not a matrix. For sparse graphs (the common case) and large n, the list is O(V + E) space; a matrix is O(Vยฒ) and dies at scale. Undirected edge [u,v] โ†’ add to both lists.
  3. DFS = stack / recursion. Plunges deep then backtracks. Natural for connectivity, cycle detection, topological sort, and "clone/mark every node." Watch recursion depth on long chains โ€” go iterative if it can be deep.
  4. BFS = queue. Expands in rings; the first arrival at a node is via the fewest edges โ†’ shortest path in unweighted graphs, level-order processing, minimum-steps problems.
  5. Sweep all components for "do it for the whole graph." A single start only reaches one component. To cover a possibly-disconnected graph, loop over all nodes and launch a fresh traversal from each unvisited one โ€” that's the connected-component sweep (and, with counting, the component count).

4. A 30-second worked example โ€” DFS vs BFS order

Same graph, same start 0, two strategies, two visit orders:

The example graph: nodes 0-4 with adjacency list

DFS (stack/recursion) dives down one branch fully before the other:

DFS traversal order 0,1,3,2,4 (deep first)

BFS (queue) clears each ring before the next:

BFS traversal order 0,1,2,3,4 (wide first)

Notice BFS reaches 3 and 4 only after everything closer โ€” which is exactly why "first time BFS sees a node" equals "shortest hop count to it." Same visited set, same adjacency list; only the frontier structure differs.


5. Problems in this chapter

โ–ถ Clone Graph

Deep-copy a connected, cyclic, undirected graph (think: snapshot a service-mesh topology into a sandbox). Traverse once carrying an original โ†’ copy map that doubles as the visited set, the cycle-breaker, and the shared-structure guarantee. The key move: record a node's copy before recursing into its neighbours.
Pattern: DFS/BFS + clone map. Target: O(N + M) time, O(N) space.

โ–ถ Find if Path Exists in Graph

Decide whether two vertices sit in the same connected component (think: can a packet cross a datacenter fabric?). Flood-fill from the source with a visited set until you touch the destination or the frontier empties.
Pattern: DFS/BFS flood-fill; Union-Find when there are many queries or incremental edges. Target: O(V + E) (BFS/DFS), O(V + Eยทฮฑ(V)) (Union-Find).


6. Common pitfalls ๐Ÿšซ

  • No visited set โ€” guaranteed infinite loop on any cycle (and undirected graphs are inherently cyclic). The single most common graph bug.
  • Adjacency matrix at scale โ€” O(Vยฒ) space is fatal past a few thousand nodes; use a list for sparse graphs.
  • Forgetting undirected edges go both ways โ€” add [u,v] to both u's and v's neighbour lists, or half your graph vanishes.
  • Recording in the clone map too late โ€” for Clone Graph, insert the copy before recursing, or cycles still loop forever.
  • Assuming one start covers the graph โ€” a disconnected graph needs a per-component sweep; one launch only floods one island.
  • Reaching for BFS shortest-path on weighted edges โ€” BFS only gives shortest paths when every edge counts the same; weights need Dijkstra/0-1 BFS.

7. Key takeaways

  1. One template, two strategies. Visited set + adjacency list + a frontier โ€” stack/recursion makes it DFS, a queue makes it BFS.
  2. The visited set is the load-bearing piece. It breaks cycles, kills redundant work, and guarantees termination; never traverse without it.
  3. BFS gives shortest unweighted paths for free โ€” first arrival = fewest edges. DFS is the natural fit for connectivity, cycles, and topological order.
  4. Reachability = same connected component, and a graph deep-copy = traversal + an original โ†’ copy map. Both problems here are this one template wearing different clothes.
  5. Why interviewers lean on this family: it's the foundation every harder graph algorithm (Dijkstra, topological sort, Union-Find, SCC) builds on โ€” and it maps directly to real systems work, from GC to partition detection.

Clone Graph

Core idea: To deep-copy a graph you must traverse it exactly once while keeping an original โ†’ copy map. The map is the whole trick: it lets you find the existing twin instead of making a new one, which both stops infinite loops on cycles and guarantees that two original nodes pointing at the same neighbour end up pointing at the same single copy.


1. The problem, in a fresh setting

Your team runs a service mesh. Every microservice is a node; a bidirectional link means "these two services talk to each other." Before a risky migration you want a sandbox replica of the live topology โ€” a brand-new graph of brand-new node objects with the same values and the same wiring โ€” so you can rewire and fault-inject the sandbox without touching the production graph. Sharing even one node object would be a disaster: a change in the sandbox would leak into production.

Formally: given a reference to one node of a connected, undirected graph, return a deep copy. Each node has an integer val and a list of neighbour nodes. The copy must share no node objects with the original, yet mirror every value and every edge.

class Node:
    def __init__(self, val=0, neighbors=None):
        self.val = val
        self.neighbors = neighbors or []
Original (val: neighbours) Deep copy returns Note
1: [2,3], 2: [1,3], 3: [1,2] (triangle) new 1',2',3' wired the same cycles everywhere
1: [] (lone node) new 1' with [] single node, no edges
None (empty graph) None nothing to copy

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account โ€” no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.