task
Given the root of a binary tree, return its level order traversal: a list of lists, one inner list per depth, values left to right. LeetCode #102 — the first tree problem in this book, so the node type is worth writing out in full.
3
/ \
9 20
/ \
15 7
level order → [[3], [9, 20], [15, 7]]
how to think
Every traversal so far in this pattern has walked a flat structure with a queue; a tree is just a
graph where “neighbors” means .Left and .Right instead of grid offsets or an adjacency list.
The one thing level order needs that plain BFS doesn’t give you for free is the level boundary —
not just every node in queue order, but every node grouped by depth.
The trick: before draining the queue for real, snapshot queue.Count. That number is exactly how
many nodes belong to the current level, because everything currently in the queue was enqueued by
the previous level and nothing else has been added yet. Process exactly that many, and whatever
gets enqueued along the way becomes next level’s batch — never mixed into this one.
template instance
BFS skeleton, on a tree. Invariant: at the top of each while iteration, queue.Count is
exactly the number of nodes at the current depth. What varies: neighbors come from .Left/.Right
instead of a direction array, and there’s no visited set — a tree has no cycles, so nothing can
ever be enqueued twice.
solution
public IList<IList<int>> LevelOrder(TreeNode? root)
{
var result = new List<IList<int>>();
if (root is null) return result;
Queue<TreeNode> queue = new();
queue.Enqueue(root);
while (queue.Count > 0)
{
int size = queue.Count; // everyone currently queued belongs to this level
var level = new List<int>();
for (int i = 0; i < size; i++)
{
TreeNode node = queue.Dequeue();
level.Add(node.Val);
if (node.Left is not null) queue.Enqueue(node.Left);
if (node.Right is not null) queue.Enqueue(node.Right);
}
result.Add(level);
}
return result;
}
// standard interview definition
public class TreeNode(int val = 0, TreeNode? left = null, TreeNode? right = null)
{
public int Val = val;
public TreeNode? Left = left;
public TreeNode? Right = right;
}
trace
3
/ \
9 20
/ \
15 7
| round | queue before round | level width | processing | level result |
|---|---|---|---|---|
| 0 | [3] |
1 | dequeue 3 → enqueue 9, 20 |
[3] |
| 1 | [9, 20] |
2 | dequeue 9 (no children); dequeue 20 → enqueue 15, 7 |
[9, 20] |
| 2 | [15, 7] |
2 | dequeue 15 (no children); dequeue 7 (no children) |
[15, 7] |
Queue is empty after round 2 — loop ends. result = [[3], [9, 20], [15, 7]].
why it works
The invariant is the whole proof: size = queue.Count, captured once per round, always equals
exactly the nodes at the current depth. That holds by induction — round 0 starts with just the root
(depth 0, count 1); every node dequeued during round k enqueues only its children, which are
depth k+1, so by the time round k finishes, the queue holds exactly the depth-(k+1) nodes and
nothing else. Grabbing queue.Count before the inner loop starts is what keeps this round’s
dequeues from also draining next round’s enqueues.
common bugs
- Reading
queue.Countinside the innerforloop instead of snapshotting it once before — after the firstEnqueuethe count changes, the loop bound drifts, and levels bleed into each other. - Forgetting the
root is nullguard — enqueueing anullroot either throws or corrupts every dequeue after it. - Dequeuing without checking
node.Left/node.Rightfornullbefore enqueueing them — crashes on any leaf. - Reaching for DFS with a
depthparameter and grouping by depth afterward — it works, but it’s the wrong tool: BFS gives you the grouping for free, in the order you want it in.
variant: right side view
Same BFS, same level-boundary trick — the only change is what you keep from each round. The
rightmost node at each depth is whichever one gets dequeued last in that round, so instead of
collecting every value, keep only the one at i == size - 1:
public IList<int> RightSideView(TreeNode? root)
{
var result = new List<int>();
if (root is null) return result;
Queue<TreeNode> queue = new();
queue.Enqueue(root);
while (queue.Count > 0)
{
int size = queue.Count;
for (int i = 0; i < size; i++)
{
TreeNode node = queue.Dequeue();
if (i == size - 1) result.Add(node.Val); // last dequeue this round = rightmost at this depth
if (node.Left is not null) queue.Enqueue(node.Left);
if (node.Right is not null) queue.Enqueue(node.Right);
}
}
return result;
}
On the same tree this returns [3, 20, 7] — round 0’s only node, round 1’s last dequeue (20,
not 9), round 2’s last dequeue (7). LeetCode #199, a one-line change to a template you already
trust, beats writing a new recursive DFS-with-depth-tracking solution from scratch.
variants you can now solve
- Average of Levels in Binary Tree (LC 637) — same round loop, average
levelinstead of returning it raw. - N-ary Tree Level Order Traversal (LC 429) — identical shape,
node.Childrenreplaces.Left/.Right. - Maximum Depth of Binary Tree (LC 104) — depth is just
result.Counthere; that page solves it with postorder DFS instead. - Binary Tree Zigzag Level Order Traversal (LC 103) — alternate
level.Addandlevel.Insert(0, ...)per round.