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]]

The root forms level 0, nodes 9 and 20 form level 1, and nodes 15 and 7 form level 2.

Input: root = [1]

Output: [[1]]

A single node is the only level.

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 is null.
  • 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.length before the inner loop.
  • Repeat levelSize times.
  • Pop one node and append its value to levelValues.
  • Enqueue its non-null left and right children.
  • Append levelValues to answer.

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.length in the inner-loop condition.
  • Updating levelSize after 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

Code
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 answer

Why 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 null children, 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

ApproachLevel boundaryOutcome
Snapshot `levelSize`Fixed before dequeuesCorrect groups: `[[3],[9,20],[15,7]]`
Use `queue.length` continuouslyChanges as children enterMixes 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 levelSize work 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.

BFS graph traversal
1 / 12
Initialize the queue with the root node 3.
Loading graph...

Queue

3

Visited

none
Every queued node is discovered but not yet processed.
1 / 12

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 == null before seeding the queue.
  • Store levelSize = queue.length once per outer iteration.
  • Use a counter from 0 through levelSize - 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 null pointers.
  • 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 space O(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.

Complexity analysis
Time
O(n)
Space
O(n)
Where the time goes
  • Visit each node onceEach node is dequeued exactly once.dominantO(n)
  • Inspect child pointersEach node has at most two children.dominantO(n)
  • Maintain queue and grouped outputThe queue and answer together store linear information.dominantO(n)
How it grows
input size n →opsO(n²)O(n log n)O(n)O(log n)O(1)
Roughly what that costs
n101001,00010,000
O(n)101001K10K

Linear time is optimal because every node must be inspected, and linear space preserves the queue and result.

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

Related problems