Tree BFS (Level Order)
Visit a tree row by row with a FIFO queue, snapshotting the queue size at the start of each row so per-level answers (averages, last node, zigzag, width) fall out naturally.
Instinct The answer talks about levels, rows, depth order, "closest to the root" or what you see from a side? Queue the tree and process it one level at a time.
Memory hack Freeze the size, drain the row - read n = queue.size() before the loop and one level never bleeds into the next.
At a glance
When to use it
- The answer is grouped by level, row or depth
- You need one node per row, such as the right side view
- You want the shallowest node or the first level that meets a condition
- Nodes on the same level must be linked or measured, like next pointers or width
Key idea
Run a FIFO queue from the root and freeze its size at the start of each round, so exactly one level is drained before the next begins; every node is touched once for O(n) time, and shallow answers stop early instead of exploring the whole tree.
Typical complexity
O(n) time, O(w) space where w is the widest level
| # | Common problems | Level | Our solution | Practice |
|---|---|---|---|---|
| 1 | Binary Tree Level Order Traversal | Medium | Read solution | LeetCode ↗ |
| 2 | Binary Tree Right Side View | Medium | Read solution | LeetCode ↗ |
| 3 | Binary Tree Zigzag Level Order Traversal | Medium | Read solution | LeetCode ↗ |
| 4 | Populating Next Right Pointers in Each Node II | Medium | Read solution | LeetCode ↗ |
| 5 | Maximum Width of Binary Tree | Medium | Read solution | LeetCode ↗ |
The instinct
Depth-first search dives down one branch and only comes back up later, so it scatters the nodes of one level across time. Breadth-first search uses a queue, which hands nodes back in exactly the order they were discovered: the whole of level 0, then all of level 1, then level 2. Whenever the question is phrased in terms of rows, depth, or "the first node you meet", BFS gives the answer in the order you need it, without extra bookkeeping.
Recognise it
| Signal in the problem | Why it points here |
|---|---|
| "Return the values level by level" / "list of lists by depth" | Literally a level-order traversal |
| "Right side view", "left view", "last node in each row" | Pick the first or last node of each drained level |
| "Zigzag", "alternate direction every level" | Same BFS, reverse (or add-to-front) on odd levels |
| "Minimum depth", "nearest leaf", "first level where..." | BFS stops at the first level that satisfies the condition - no need to explore deeper |
| "Average / max / sum of each level", "largest value in each row" | Aggregate inside the per-level loop |
| "Connect each node to its next right node" | Nodes of a level come out of the queue left to right, consecutively |
| "Maximum width", "is the tree complete" | Track positional indices per level or detect a gap after a null |
Not this pattern if...
- The answer combines information from children up to the parent (height, diameter, path sums): that is Tree DFS.
- You need sorted order of a BST: that is in-order traversal.
- The "levels" are in a general graph with cycles: same idea, but you also need a visited set (graph BFS).
Memory hack
"Freeze the size, drain the row." At the top of every round, read n = queue.size(). Exactly those n nodes are the current row; their children, added during the loop, belong to the next round.
Analogy - boarding a ferry by rows. Passengers wait in a single queue. The usher counts how many are standing at the gate right now, lets precisely that many board as one row, and anyone who arrives while they board waits for the next sailing. Counting first is what keeps the rows clean.
Say it out loud in the interview:
- "This is about levels, so I'll BFS with a queue, seeded with the root (guarding against a null root)."
- "Each round I snapshot the size, pop that many, do the per-level work, and push non-null children."
- "After the inner loop, I record the level result - first/last node, sum, list, or width."
How it works
Trace on the tree 3 -> (9, 20), 20 -> (15, 7):
| Round | Queue at start | Size frozen | Popped this round | Children pushed | Level result |
|---|---|---|---|---|---|
| 0 | [3] | 1 | 3 | 9, 20 | [3] |
| 1 | [9, 20] | 2 | 9, 20 | 15, 7 | [9, 20] |
| 2 | [15, 7] | 2 | 15, 7 | - | [15, 7] |
| end | [] | - | - | - | [[3],[9,20],[15,7]] |
Right side view takes the last value of each row (3, 20, 7). Zigzag reverses round 1 to [20, 9]. Minimum depth would stop at round 1 if 9 were a leaf.
flowchart TD
A["queue = [root]"] --> B{"queue empty?"}
B -->|no| C["n = queue.size()"]
C --> D["pop n nodes, push their children"]
D --> E["record level answer"]
E --> B
B -->|yes| F["return answer"]
Java template
import java.util.*; class TreeNode { int val; TreeNode left, right; TreeNode(int v) { val = v; } } class TreeBfs { // Generic level-order skeleton. static List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) return result; Deque<TreeNode> queue = new ArrayDeque<>(); queue.offer(root); int depth = 0; while (!queue.isEmpty()) { int n = queue.size(); // freeze the row List<Integer> level = new ArrayList<>(n); for (int i = 0; i < n; i++) { TreeNode node = queue.poll(); level.add(node.val); // per-node work (i == n - 1 is the rightmost) if (node.left != null) queue.offer(node.left); if (node.right != null) queue.offer(node.right); } if (depth % 2 == 1) { /* zigzag: Collections.reverse(level); */ } result.add(level); // per-level work depth++; } return result; } // Width variant: carry a positional index, re-based per level to avoid overflow. static int maxWidth(TreeNode root) { if (root == null) return 0; Deque<Object[]> queue = new ArrayDeque<>(); queue.offer(new Object[]{root, 0L}); int best = 0; while (!queue.isEmpty()) { int n = queue.size(); long first = (long) queue.peek()[1], last = first; for (int i = 0; i < n; i++) { Object[] cur = queue.poll(); TreeNode node = (TreeNode) cur[0]; long idx = (long) cur[1] - first; // normalise so indices stay small last = idx; if (node.left != null) queue.offer(new Object[]{node.left, 2 * idx}); if (node.right != null) queue.offer(new Object[]{node.right, 2 * idx + 1}); } best = Math.max(best, (int) (last + 1)); } return best; } }
Complexity
- Time: O(n) - every node is enqueued and dequeued once.
- Space: O(w), where w is the widest level. For a complete tree the last level holds about n/2 nodes, so the worst case is O(n); for a skinny, list-like tree it is O(1). (DFS is the mirror image: O(h) stack.)
Variations
- Plain level order / bottom-up level order - collect rows; reverse the final list for bottom-up. Binary Tree Level Order Traversal.
- Pick one per row - first or last node of each round. Binary Tree Right Side View.
- Direction flip - reverse odd rows, or use a deque and add to the front. Binary Tree Zigzag Level Order Traversal.
- Sibling linking - keep a
prevpointer within the round; an O(1)-space version walks the already-linked level above like a linked list. Populating Next Right Pointers in Each Node II. - Positional index - heap-style indices
2iand2i+1, re-based each level. Maximum Width of Binary Tree. - Early exit - stop at the first level where a leaf or target appears. Minimum depth, nearest target.
- Multi-source / distance K - convert the tree to a graph (add parent links) and BFS from a start node.
Common mistakes
Writing for (int i = 0; i < queue.size(); i++) re-reads a size that grows while you push children, so levels merge. Store the size in a variable before the loop.
- Forgetting the
root == nullguard, then callingqueue.offer(null)-ArrayDequethrows aNullPointerException. - Pushing null children and not checking them after polling; either skip nulls at push time or handle them consistently (some "complete tree" checks push nulls on purpose).
- Using raw heap indices for width on deep, skewed trees:
2i + 1doubles each level and overflows even alongafter ~63 levels unless you re-base per level. - Picking BFS for problems that need children's results first (diameter, balanced check) - you end up storing everything and reconstructing a DFS by hand.
- Reversing the queue itself for zigzag instead of just the collected row - this scrambles the order of the next level.
All practice problems
5 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.