Data structures & algorithms
Graphs: representation, BFS and DFS
A graph is a list of who is next to whom. Build one from an edge list, then run BFS and DFS from node 0 on the same six-node graph — same ten lines, one container swapped, two different visit orders.
Swap the queue for a stack yourself →01 The idea
Nodes, edges, and a frontier of things to look at next
A graph is a set of things plus the connections between them. The things are nodes; the connections are edges. Cities and roads, users and friendships, board positions and legal moves — once you see the graph, the same two traversals point at all of them.
You already know a special case. A tree is a graph that is connected and has no cycles, so there is exactly one path between any two nodes. A general graph drops both restrictions, which is why graph traversal needs a visited set and walking a rooted tree does not: a tree’s child pointers only ever point down, while graph edges run both ways and a single edge would send you back and forth forever.
02 Hand trace
From an edge list to a BFS order
Seven edges, written the way input actually arrives: 0-1 1-2 2-3 3-4 4-5 5-0 0-3. Six nodes in a ring, one chord across from 0 to 3. It is undirected, so every edge goes into the list twice, once at each end.
Node 0 is where every traversal in this lesson starts. The dashed chord is the only edge that is not part of the ring, and it is what stops the graph being a simple cycle.
The list stores two numbers per edge. The matrix stores a cell for every possible edge, whether or not it is there — which is why listing one node’s neighbours costs O(V) on a matrix and O(deg u) on a list.
Now run BFS from node 0. Each row is one node coming out of the queue. The boxes are the queue contents after that node was taken and its neighbours added; the rose box is the front, the node that comes out next. A dot means empty.
Read the queue column, not the order column. Node 0 puts all three neighbours in at once, so 1, 3 and 5 all come out before anything two edges away is even discovered. Redraw the graph by distance and that becomes the whole picture.
The five solid edges are the BFS tree; the two dashed ones lead somewhere already seen and discover nothing. BFS empties an entire level before the next one starts, which is exactly why the level a node appears on is its shortest distance.
03 Code
One loop, two containers
The trace you just followed is ten lines. Here it is twice. Apart from the name on line 1, the two blocks differ on line 3 and line 5, in one word each, and nowhere else.
BFS · frontier is a queue
function bfs(adj, start): seen = {start}; order = [] frontier = Queue([start]) # container while frontier is not empty: node = frontier.takeFront() # oldest out order.append(node) for nb in adj[node]: if nb not in seen: seen.add(nb); frontier.push(nb) return order
DFS · frontier is a stack
function dfs(adj, start): seen = {start}; order = [] frontier = Stack([start]) # container while frontier is not empty: node = frontier.takeTop() # newest out order.append(node) for nb in adj[node]: if nb not in seen: seen.add(nb); frontier.push(nb) return order
Same three nodes, same order of arrival. The only question either container answers is which end you take from, and that single choice is the entire difference between the two traversals.
Only the container changes. On this graph that gives BFS 0 1 3 5 2 4 and DFS 0 5 4 3 2 1 — same graph, same start, same seven edges, same number of steps, different order.
BFS takes 0’s three neighbours first and only then goes deeper. DFS walks right round the ring instead: adj[0] is [1, 3, 5], so the stack pushes 1, then 3, then 5 — and 5 is now on top.
Mark visited on line 9, when you push — not when you pop. Node 2 is a neighbour of both 1 and 3. Marking at push time means 3 finds it already seen and it enters the queue exactly once. Marking at pop time keeps the order and the O(V + E) time, but a node can be pushed once per neighbour that finds it, so the frontier grows to O(E) instead of O(V).
The stack version is not the recursion with the neighbours flipped. Marking at push time claims a node the moment anyone finds it, so the walk can be cut off from below. Start the console at node 3: the stack gives 3 4 5 2 1 0, which no recursive DFS can produce. Both are correct depth-first traversals; they are simply not the same order.
Recursive DFS is the same idea with the call stack doing the work. dfs(node): seen.add(node); order.append(node); for nb in adj[node]: if nb not in seen: dfs(nb). The frontier is now the pile of pending call frames, so graph depth is your memory bill and a chain of 100,000 nodes will overflow it.
05 Cheat sheet
The numbers you get asked for
| What you ask of the graph | Adjacency list | Adjacency matrix |
|---|---|---|
| Memory for V nodes and E edges | O(V + E) | O(V²) |
| Are u and v joined? | O(deg u) | O(1) |
| List u’s neighbours | O(deg u) | O(V) |
| One full BFS or DFS | O(V + E) | O(V²) |
| Add an edge | O(1) | O(1) |
| This lesson’s graph, V = 6 and E = 7 | 14 numbers | 36 numbers |
06 Where & why
Picking the container on purpose
BFS and DFS cost the same and reach the same nodes. They are still not interchangeable, because of what they hand you on the way. Pick by the information you need, not by which one you remember first.
Reach for BFS when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| You need the order calls enter and leave — directed-cycle detection, or a topological order off finish times | Recursive DFS | Neither the queue nor the push-marking stack produces entry and exit times; recursion gives them free. |
| You ask whether two nodes are connected over and over, with edges arriving as you go | Union-find | Near-constant time per query with no traversal, merging components as edges arrive. For one question on a fixed graph, one traversal is simpler. |
| Edges carry different weights | Dijkstra | BFS counts edges, not cost, so it hands you a two-edge path that is dearer than a three-edge one. |
| The graph is dense — E close to V² | An adjacency matrix | The list stops saving memory, and the matrix answers “are u and v joined?” in O(1). |
07 Interview questions
Say these out loud
Question 7 is the one that separates people who have written BFS from people who have only read it.
What is a graph, and how is a tree different?
Adjacency list or adjacency matrix — which do you use?
How do you build an adjacency list from an edge list?
What is the actual difference between BFS and DFS?
Time and space complexity of BFS and DFS?
Why does BFS give the shortest path on an unweighted graph, and DFS not?
Where exactly do you mark a node visited?
What happens if you drop the visited set entirely?
How do you count connected components?
Your iterative DFS gives a different order to your recursive DFS — is one wrong?
Is “number of islands” really a graph problem?
When would you use neither BFS nor DFS?
08 Practice problems
Build it, then traverse it
For each one, decide what the nodes are and what the edges are before you write a line, then name the container. Everything here is solvable with the ten lines from section 03 and a little bookkeeping.