Binary Tree Level Order Traversal
BFS a level at a time by draining the queue's current size.
Approach
Use a queue seeded with the root. At each level, record the current queue size, then dequeue exactly that many nodes (collecting their values and enqueueing their children). Each batch is one level's output.
Time complexity
O(n)
Space complexity
O(n)
Common mistake
Not snapshotting the queue length before the inner loop, which mixes nodes from two levels together.
Interactive walkthrough — step through it below, free and without an account
Problem
Given the root of a binary tree, return its node values grouped by depth from top to bottom. Within each depth, list values from left to right. An empty tree should produce an empty list.
Examples
Input: root = [3,9,20,null,null,15,7]
Output: [[3],[9,20],[15,7]]
Input: root = [1]
Output: [[1]]
Problem
Problem
Given the root of a binary tree, return its node values grouped by depth from top to bottom. Within each depth, list values from left to right. An empty tree should produce an empty list.
Problem and Goal
Learning goal: Recognize level order traversal as a breadth-first search that preserves the tree's depth structure.
Core idea A binary tree has natural layers: the root is level 0, its children are level 1, and so on. We want one output array per layer, with values appearing from left to right. A queue is ideal because nodes are processed in the same order they are discovered.
Algorithm plan
- Return
[]immediately when the root isnull. - Put the root into a queue.
- Repeatedly process one complete level.
- Append each level's collected values to the answer.
Micro-example
For [3,9,20,null,null,15,7], begin with queue [3].
The first batch produces [3]; its children prepare the next batch [9,20].
Pitfalls
- Forgetting the empty-tree case.
- Processing a child in the same output batch as its parent.
- Reversing left and right child insertion order.
Recap
- BFS naturally visits nodes by increasing depth.
- The queue controls left-to-right order.
- The key challenge is separating batches into levels.
Problem and Goal
Group values by depth
The answer is `[[3],[9,20],[15,7]]`: each inner array contains exactly one tree level, ordered from left to right.
Do not flatten the BFS visit order; preserve its level boundaries.
Key Idea: Drain the Current Queue Size
Learning goal: Understand why snapshotting the queue length separates one tree level from the next.
Core idea
At the beginning of a level, the queue contains exactly the nodes belonging to that level. Save this count as levelSize. Dequeue exactly levelSize nodes; while doing so, enqueue their children. Those newly enqueued children belong to the next level and must not be consumed until the next outer-loop iteration.
Key insight
The queue grows during a level, but levelSize does not. That frozen boundary is what makes each output batch correct.
Algorithm plan
- Set
levelSize = queue.lengthbefore the inner loop. - Repeat
levelSizetimes. - Pop one node and append its value to
levelValues. - Enqueue its non-null left and right children.
- Append
levelValuestoanswer.
Micro-example
Queue [9,20] means the next level has two nodes, so levelSize = 2.
Even after processing 20 and adding 15 and 7, the loop still stops after two dequeues.
Pitfalls
- Reading
queue.lengthin the inner-loop condition. - Updating
levelSizeafter children are enqueued. - Treating the queue's changing size as the current level's size.
Recap
- Snapshot first:
levelSize = queue.length. - Drain exactly that many nodes.
- Children wait in the queue for the next batch.
Key Idea: Drain the Current Queue Size
Remove exactly `levelSize` nodes, even if children are appended.
Main Approach and Correctness
Learning goal: Translate the level-boundary idea into a complete BFS algorithm.
Core idea
The queue contains a frontier of discovered but unprocessed nodes. At each outer iteration, its first levelSize entries are precisely the current depth because every parent was processed during the previous iteration. Processing those entries left to right records the level, while appending children creates the next frontier.
Algorithm plan
if root is null: return []
queue = [root]
answer = []
while queue is not empty:
levelSize = queue.length
levelValues = []
repeat levelSize times:
node = dequeue()
append node.value to levelValues
enqueue node.left if it exists
enqueue node.right if it exists
append levelValues to answer
return answerWhy it works
The queue starts each iteration with exactly one depth's nodes. Removing all of them before the next iteration makes every output array correspond to one level.
Micro-example
Starting with [3], snapshot 1 and output [3].
Then [9,20] has snapshot 2 and outputs [9,20], while [15,7] waits.
Pitfalls
- Enqueueing
nullchildren, which creates meaningless work. - Omitting the left-before-right order.
- Returning the queue contents instead of grouped
levelValues.
Recap
- Outer loop means “one level.”
- Inner loop drains the saved count.
- Each child is enqueued once, giving linear work.
Main Approach and Correctness
| Approach | Level boundary | Outcome |
|---|---|---|
| Snapshot `levelSize` | Fixed before dequeues | Correct groups: `[[3],[9,20],[15,7]]` |
| Use `queue.length` continuously | Changes as children enter | Mixes parent and child levels |
Walkthrough: One Tree End to End
Learning goal: Trace the queue, level size, and output across the complete example.
Core idea
Use the tree 3 with children 9 and 20; node 20 has children 15 and 7. The queue shows the frontier, while levelSize records how many nodes belong to the current batch. Every dequeue contributes one value to the current level, and every enqueue prepares future work.
Algorithm plan
- Seed the queue with node
3. - Snapshot and drain one level at a time.
- Enqueue left children before right children.
- Stop when the queue is empty.
Micro-example
[3], size 1 → output [3], enqueue 9,20.
[9,20], size 2 → output [9,20], enqueue 15,7.
[15,7], size 2 → output [15,7], then finish.
Read the queue carefully
When processing level [9,20], the queue may grow to [15,7]; those nodes are not part of the current batch because the saved size is still 2.
Pitfalls
- Counting nodes after enqueuing children.
- Assuming the queue is always the current level only; it also holds the next frontier.
- Losing left-to-right order by enqueuing right children first.
Recap
- Queue state and
levelSizework together. - The example produces three batches.
- The final answer is
[[3],[9,20],[15,7]].
Walkthrough: One Tree End to End
Animates level-order BFS on the example tree while showing the frontier and frozen level sizes.
Pitfalls and Edge Cases
Learning goal: Diagnose the queue-length mistake and handle boundary cases confidently.
Core idea The most important implementation detail is taking the queue-length snapshot before the inner loop. If the inner loop checks the live queue length, newly enqueued children extend the loop and get mixed into their parent's level. The algorithm still visits every node, but the grouping becomes wrong.
Algorithm plan
- Check
root == nullbefore seeding the queue. - Store
levelSize = queue.lengthonce per outer iteration. - Use a counter from
0throughlevelSize - 1. - Add only existing children.
- Preserve left child, then right child order.
Common pitfall
Do not write an inner loop based on the changing queue.length; snapshot it first, or nodes from two levels will be mixed together.
Micro-example
For queue [9,20], the correct snapshot is 2.
After processing 20, children [15,7] may be present, but they must wait for the next outer iteration.
Pitfalls
- Empty tree: return
[], not[[]]. - Single node: return
[[value]]. - Sparse children: skip
nullpointers. - Live-length loop: incorrectly turns multiple levels into one batch.
Recap
- Snapshot before dequeueing.
- Queue growth is expected, not an error.
- Edge cases follow directly from the same invariant.
Pitfalls and Edge Cases
When processing queue [9,20], which loop bound correctly completes the current level?
Complexity and Wrap-up
Learning goal: Explain why queue-based level order traversal runs in linear time and uses linear auxiliary space.
Core idea
Every node enters the queue once and leaves it once. The work performed while processing a node is constant aside from examining its two child pointers, so the total time is O(n). The queue can contain an entire level, and the answer itself also stores all n values, giving O(n) space overall.
Algorithm plan
- Count each enqueue and dequeue once.
- Treat child checks as constant work.
- Include the output and queue in the space discussion.
- Report the target bounds as time
O(n)and spaceO(n).
Micro-example For five nodes, five nodes are dequeued and each has at most two child checks. The answer stores five values grouped into three arrays.
Why this is optimal
A correct traversal must inspect every node, so O(n) time is unavoidable. BFS matches that lower bound while directly producing level groups.
Pitfalls
- Calling the space
O(1)while ignoring the queue and output. - Confusing tree height with the number of stored nodes.
- Claiming
O(n^2)because there is a nested loop; the inner work is partitioned across nodes.
Recap
- Time:
O(n). - Auxiliary queue and output:
O(n)space. - Snapshotting the queue length is the central correctness invariant.
Complexity and Wrap-up
Summarizes the cost of BFS level order traversal and why the nested loops remain linear.
Now try it on your own problem
Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.
Build a lesson from any problem
