3.4.3 · D2Trees

Visual walkthrough — Level-order traversal — BFS with queue

2,301 words10 min readBack to topic

Step 1 — The wish: read a tree floor by floor

WHAT. A tree is a picture where one dot sits on top (the root), and lines drop down to children dots, which drop down to their children, and so on. A dot is a node. The level (also called depth) of a node is how many lines down from the root it sits: the root is level , its children are level , their children level .

WHY. Before we can invent an algorithm we must state exactly what output we want. Our wish is: print every node on level , then every node on level , then level … and left-to-right inside each floor. That word floor is the whole goal — everything else is machinery to achieve it.

PICTURE. Look at the tree below. The dashed horizontal bands are the floors. The desired reading order is the number written inside each node, and it sweeps each band left-to-right before dropping to the next.

Figure — Level-order traversal — BFS with queue

Step 2 — The obstacle: you can only see kids after you meet the parent

WHAT. When we stand on a node, the only new nodes we can "reach" are that node's own children. We cannot magically jump to a cousin two floors down — we discover the tree by walking its lines.

WHY. This is the crux. We want to output floor by floor, but we discover nodes parent-by-parent. So there is a gap between discovering a node and being ready to process it. Anything with a gap like that needs a place to park the discovered-but-not-yet-processed nodes. That parking lot is the data structure we are about to invent.

PICTURE. The red arrows show the only moves available from node 2: down-left to 4, down-right to 5. We found 4 and 5, but we are not allowed to print them yet — floor isn't finished (node 3 still waits). So 4 and 5 must go into a holding area (the grey box).

Figure — Level-order traversal — BFS with queue

Step 3 — Which serving rule? FIFO vs LIFO, tested on the picture

WHAT. A parking lot for pending nodes can hand them back in one of two natural orders:

  • FIFO (First-In-First-Out): the one that arrived earliest leaves first. This is a queue — a polite line at a shop.
  • LIFO (Last-In-First-Out): the one that arrived most recently leaves first. This is a stack — a pile of plates.

WHY. We have to choose, and the choice is not free — it decides the whole output. So let's not guess; let's run both on the same tiny tree and watch. We enqueue the root, then repeatedly take one out, print it, and put its children in.

PICTURE. Two side-by-side traces on the same tree. On the left, FIFO serves 1, then 2, then 3 (floor finished!) before touching 4,5,6. On the right, LIFO serves 1, then dives straight into 3's side, printing 3 then 6 before it ever gets back to 2 — floors are shredded.

Figure — Level-order traversal — BFS with queue

Step 4 — Prove FIFO really gives floors (the fence argument)

WHAT. We claim: with a queue, all of level comes out before any of level . Let's see it, not just assert it.

WHY. "It looked right on one tree" is not a proof. We want to know it works on every tree. The picture below shows the key invariant: at the moment level is fully inside the queue, level is completely absent — because level- nodes are the children of level- nodes, and children only enter after their parent is served.

PICTURE. The queue is drawn as a horizontal line, front on the left. The yellow block is all of level , sitting contiguously. As we serve each yellow node from the front, its green children (level ) append to the back — always behind every remaining yellow node. So the whole yellow block empties before a single green node reaches the front. A clean fence separates the floors.

Figure — Level-order traversal — BFS with queue

Step 5 — The algorithm writes itself

WHAT. Steps 1–4 hand us the code with no further invention.

WHY. Each line below is a consequence we already justified: seed the root (Step 1), loop while pending nodes remain (Step 2), serve oldest = dequeue (Step 3 FIFO), append children to the back (Step 4 fence), guard None because empty spots have no children (Step 2 — you can only push real kids).

PICTURE. The flow of one iteration: pull from the front (blue), print it (yellow), push its children to the back (green).

Figure — Level-order traversal — BFS with queue

Step 6 — Grouping by floor: the size snapshot

WHAT. Flat BFS gives one long list. To get one list per floor (like [[1],[2,3],[4,5,6]]), we freeze the queue's length at the start of each round.

WHY. From Step 4 we know: at the start of a round, the queue holds exactly one floor. So its current length is that floor's node-count. If we record n = len(queue) before we start pulling, and then pull exactly n times, we drain precisely this floor — no more, no less — even though children are pouring into the back meanwhile.

PICTURE. The queue holds [2,3] (one full floor). We snapshot n = 2 (yellow bracket). We pull 2 (its kids 4,5 join the back) and pull 3 (its kid 6 joins the back). After exactly pulls we stop: the queue now holds the next floor [4,5,6], untouched.

Figure — Level-order traversal — BFS with queue

Step 7 — Edge & degenerate cases (never surprise the reader)

WHAT. We check the corners the happy path skipped.

WHY. An algorithm you trust must survive the weird inputs, not just the neat balanced tree.

PICTURE. Three corner trees and what the queue does in each.

Figure — Level-order traversal — BFS with queue
  • Empty tree (root is None). We never enqueue anything; the while never runs; output is empty. Correct — no floors to read.
  • Single node. Enqueue 1, serve 1, it has no kids, queue empties. Output [1]. Max queue size .
  • Skewed tree (a straight line 1→2→3). Each floor has exactly one node, so the queue never holds more than one node at a time. Output 1 2 3. Here BFS uses space — its worst space case is a wide tree, not a tall one.

The one-picture summary

Everything above compressed into a single frame: the tree on top, the queue as a moving line below, the fence sliding right one floor at a time, the printed output growing left-to-right.

Figure — Level-order traversal — BFS with queue
Recall Feynman retelling — the whole walkthrough in plain words (click to reveal)

You want to read a family portrait one row at a time, left to right. Trouble is, you only meet someone's children after you meet them — so you keep a waiting line. You call the person at the front, write their name, and tell their kids "go stand at the back of the line." Because every new kid joins the back, you always finish calling a whole row before any of that row's kids reach the front — the rows never mix. That "front-out, back-in" rule is a queue (FIFO); a stack would call the newest kid first and send you diving down one branch instead. If you also want the names bundled by row, just count how many people are in line before you start a round — that count is exactly this row's size, so you call exactly that many and stop. Empty tree? The line starts empty, you do nothing. One node? One call. A straight line of nodes? The waiting line is never longer than one — which is why a queue costs memory proportional to the widest row, not the tallest branch.


Active recall


Connections

  • Parent: Level-order traversal — the full note this page illustrates.
  • Breadth-First Search (BFS) — the general algorithm; this is its tree case.
  • Depth-First Search (DFS) — the stack/LIFO counterpart we ruled out in Step 3.
  • Queue (FIFO) data structure — the parking lot we were forced to invent.
  • Stack (LIFO) — the tempting-but-wrong alternative.
  • Binary Tree representation — how the nodes and left/right children are stored.
  • Shortest path in unweighted graphs — same fence argument gives shortest distances.
  • Tree height vs width — why BFS space is width, DFS space is height.