Algorithm

Binary Tree Level Order Traversal

Trees Pattern

Binary Tree Level Order Traversal

Given the root of a binary tree, return its values grouped by depth: a list of lists, where the first inner list holds the root, the second holds all nodes one level down read left to right, and so on. The grouping matters — a single flat list of the same values is not an accepted answer. An empty tree returns an empty list of levels.

CONSTRAINTS
  • The number of nodes in the tree is in the range [0, 2000]
  • -1000 <= Node.val <= 1000
  • Values within a level are ordered left to right
  • Levels are returned top to bottom
EXAMPLE 1
Input: root = [3,9,20,null,null,15,7]
Output: [[3],[9,20],[15,7]]
Three depths, three groups. 15 and 7 land in the same group despite both being 20's children, because grouping is by depth rather than by parent.
EXAMPLE 2
Input: root = [1,2,3,4,null,null,5]
Output: [[1],[2,3],[4,5]]
4 and 5 sit at the same depth under different parents, and 4 comes first because its parent is further left. The gaps between them contribute nothing to the output.
EXAMPLE 3
Input: root = [1,2,null,3]
Output: [[1],[2],[3]]
A chain gives one node per level, so every group holds a single value.
EXAMPLE 4
Input: root = []
Output: []
No nodes means no levels — an empty outer list, not a list containing an empty list.
Is a flat list of values acceptable?
No, the levels must be separated. Producing the right values in the right order but in one list is the most common near-miss here.
How should missing children be treated?
They are skipped entirely — no placeholder appears in the output. A level's list holds only the nodes that actually exist.
Can this be done with recursion instead of a queue?
Yes: a depth-first walk that appends each value to the list for its depth works fine and gives the same result. The queue version is preferred when you need levels processed strictly in order, such as for an early exit at the first level satisfying some condition.
What is the memory cost?
The queue holds at most one level at a time, so it is proportional to the tree's widest level — which for a bushy tree can be about half of all nodes.

Every traversal so far has been depth-first: commit to one branch, follow it to the bottom, then come back. That is the natural fit for recursion, and it is exactly wrong for this problem. A depth-first walk reaches a deep node on the left before a shallow node on the right, so nodes arrive with their depths interleaved. We need them grouped by depth, which means visiting all of depth 1, then all of depth 2, and so on — a fundamentally different order.

Explore outward instead of downward

Think about what we know at the start: the level-0 list is just the root. And the level-1 list is exactly the children of everything at level 0. In general, each level is the collection of children of the level before it. That is a complete recipe, and it never needs to look further ahead than one step.

So keep a collection of "nodes discovered but not yet expanded." Take nodes out of it, and put their children in. The only question is the order in which nodes come back out — and it must be the order they went in, because that is what keeps level 1's nodes from being processed before level 0's are finished.

A structure with that behaviour is a queue: items are added at one end and removed from the other, so the first in is the first out — a waiting line. (Contrast the stack from Iterative Traversals, where the last in comes out first, which is precisely what produces depth-first order. The choice of container is the choice of traversal.)

Exploring a structure this way — everything one step away, then everything two steps away — is called breadth-first search, or BFS, and it works on graphs just as well as trees.

Separating the levels

Run the queue loop naively and the values come out in the right order but as one flat stream, because the queue mixes generations: while we are expanding the last node of level 1, the first nodes of level 2 are already sitting behind it.

The fix is a small observation. At the exact moment we are about to start a new level, the queue contains precisely that level's nodes and nothing else — the previous level has been fully removed, and no node from the next level has been added yet. So take the queue's size at that instant, and that number tells us how many nodes to pull before the boundary. Children added during those pulls collect behind the line and become the next level's contents.

python
if root is None: return []
out, queue = [], deque([root])
while queue:
    level_size = len(queue)         # exactly the nodes at this depth, right now
    level = []
    for _ in range(level_size):     # pull exactly that many — no more
        node = queue.popleft()
        level.append(node.val)
        if node.left:  queue.append(node.left)    # joins the back, for the next round
        if node.right: queue.append(node.right)
    out.append(level)
return out
Crucial Notelevel_size must be captured before the inner loop and never re-read inside it. The queue's length changes constantly as children are appended, so a loop written against the live length would keep going straight into the next level and never produce a boundary at all. Freezing the count is the entire mechanism that turns a flat BFS into a level-grouped one, and it is worth recognising on sight — the same three lines appear in Zigzag, Right Side View, Maximum Width and Populating Next Right Pointers.

One detail on the queue itself: removing from the front of a plain array is O(K), because every remaining element shifts down, which would make the whole traversal O(N²) on a wide tree. Use a structure with constant-time removal at the front — collections.deque in Python, LinkedList/ArrayDeque in Java, or an array with a moving head index.

Worked example:root 3, children 9 and 20; 20 has children 15 and 7
- Queue: [3]. level_size = 1. Pull 3, record it, append 9 and 20. Level [3] is complete; queue is [9, 20].
- level_size = 2 — frozen now, even though the queue will grow during this round. Pull 9 (no children). Pull 20, appending 15 and 7. Level [9, 20] is complete; queue is [15, 7].
- level_size = 2. Pull 15 and 7, neither with children. Level [15, 7] is complete; queue is empty.
- Result: [[3], [9, 20], [15, 7]].

Watch the second round: after pulling 9 the queue held [20, 15, 7], three items — but the frozen count of 2 stopped the loop after 20, which is exactly where the level boundary belongs.

Cost, and when to reach for this

Every node is enqueued once and dequeued once, doing constant work each: O(N) time. The extra space is the queue, which holds at most one level, so it is O(W) where W is the tree's widest level — and in a full binary tree the bottom level holds about half the nodes, so W can be around N/2.

That is the real trade against depth-first search, and it is worth stating both directions. DFS costs O(H) memory, BFS costs O(W). On a chain-shaped tree, H = N while W = 1, so BFS wins outright. On a wide bushy tree, H ≈ log₂N while W ≈ N/2, so DFS wins outright. Neither is universally cheaper; it depends on the shape.

The recognition signal to carry forward: when a problem's answer is organised by depth or distance — per level, the first node at each depth, the shallowest something, the minimum number of steps — reach for a queue. Depth-first search can often be made to produce the same answer by tracking depth as a parameter, but it visits nodes in an order the problem does not care about, which makes early exits impossible. Every remaining problem in this section is this loop with one slot filled differently: Zigzag changes the order values are written into the level list, Right Side View keeps only the last node of each level, Maximum Width tracks positions instead of counts, and Populating Next Right Pointers links each node to the one behind it in the queue.

Interactive Strategy Visualization

Level Order Traversal

Horizontal BFS Logic

3920157
Queue & Path
FIFO Queue
3
[]
Result Set

"Snapshot queue size to separate levels."

Strategy

Process by 'snapshotting' queue size. each level is handled completely before the next begins.

"Generational BFS traversal."

O(N × H) Repeated Depth-Limited Walks
O(N) Time · O(W) Space Queue With Frozen Level Size