AlgoMaster Logo

Binary Tree Level Order Traversal

mediumFrequency7 min readUpdated June 23, 2026

Understanding the Problem

We have a binary tree, and we need to return its values grouped by level. All nodes at the same depth go into the same sublist, and within each sublist, nodes appear left to right. The result is a list of lists, where the first sublist contains only the root, the second contains all nodes at depth 1, and so on.

This is different from standard tree traversals (inorder, preorder, postorder) because those follow branch paths. Here, we need to process nodes "horizontally" across the tree. The core challenge is: how do we visit every node at depth d before visiting any node at depth d+1?

BFS answers it directly, since it visits nodes in order of distance from the root. DFS can also produce the grouping if each recursive call tracks the depth of the node it visits.

Key Constraints:

  • Number of nodes in range [0, 2000]: Every approach in this chapter runs in O(n) time, so performance does not separate them. The choice comes down to traversal strategy and memory profile.
  • -1000 <= Node.val <= 1000: Node values can be negative. This doesn't affect the traversal logic, but make sure your solution doesn't assume positive values.
  • The tree can be empty (0 nodes), so we need to handle the null root case.

Approach 1: DFS with Level Tracking

Intuition

DFS does not visit nodes level by level, but it can still produce the level grouping if every call knows the depth of the node it is visiting. Pass the depth as a parameter and append each node's value to result[depth]. The first time the traversal reaches a new depth, result has exactly depth sublists, so appending a fresh empty list at that moment keeps the indices aligned.

Ordering within each level also holds. The traversal recurses into the left subtree before the right one, so among nodes at the same depth, a node further to the left is visited earlier. Values therefore enter each level's sublist in left-to-right order.

Algorithm

  1. If the root is null, return an empty list.
  2. Create an empty list of lists called result.
  3. Define a recursive helper function dfs(node, level):
    • If node is null, return.
    • If level equals the current size of result, append a new empty list (we've reached a new level for the first time).
    • Append node.val to result[level].
    • Recurse on the left child with level + 1.
    • Recurse on the right child with level + 1.
  4. Call dfs(root, 0) and return result.

Example Walkthrough

root
1Start DFS at root (3), level=0
3visit920157
result
1Visit 3 at level 0: create level 0 list, add 3
[3]
1/6

Code

The DFS version recovers the grouping from the depth parameter rather than from the order of traversal. BFS visits nodes level by level, so the traversal order matches the output directly. The next two approaches are both BFS; they differ in how they mark where one level ends and the next begins.

Approach 2: BFS with Two Queues

Intuition

BFS processes every node at depth d before any node at depth d+1, so a breadth-first traversal emits values in the order the output needs. What it does not provide on its own is the boundary between levels.

Two lists make that boundary explicit. current holds the nodes of the level being processed, and next collects their children. One pass over current produces one sublist of the result. When the pass finishes, next contains the entire following level in left-to-right order, because children were appended in the order their parents appear in current. Replacing current with next moves the traversal down one level, and the loop ends when a level produces no children.

Algorithm

  1. If the root is null, return an empty list.
  2. Create a list current containing only the root.
  3. While current is not empty:
    • Create an empty list next for child nodes and an empty list values for this level's values.
    • For each node in current: append node.val to values, then append the node's non-null children (left child first) to next.
    • Append values to the result and set current = next.
  4. Return the result.

Example Walkthrough

root
1current = [3]: record level 0 values
3visit920157
current
1current holds level 0
3
result
1values = [3] appended: result = [[3]]
[3]
1/6

Code

The only overhead in the two-queue version is allocating a fresh node list for every level. A single queue can carry the same information: recording the queue's size before a level starts tells the loop how many dequeues belong to that level.

Approach 3: BFS with Level-Size Counting (Optimal)

Intuition

Seed a queue with the root. At the start of each outer iteration, the queue holds exactly the nodes of one level, in left-to-right order. The invariant holds by induction: it is true at the start (the queue holds only the root), and each iteration dequeues every node of the current level while enqueueing their children in order, so the next iteration starts with exactly the next level.

The size snapshot is what preserves the invariant. Recording levelSize = queue.size() before the inner loop fixes how many dequeues belong to the current level. Everything enqueued during those dequeues belongs to the next level and stays in the queue for the following iteration.

A variant marks the boundary differently: enqueue a sentinel (such as null) after each level and start a new sublist whenever the sentinel reaches the front of the queue. The size snapshot gives the same separation without placing non-node values in the queue.

Algorithm

  1. If the root is null, return an empty list.
  2. Create a queue and add the root to it.
  3. While the queue is not empty:
    • Record the current queue size as levelSize (the number of nodes in this level).
    • Create an empty list for the current level.
    • Process exactly levelSize nodes from the queue:
      • Dequeue a node, add its value to the current level's list.
      • If the node has a left child, enqueue it.
      • If the node has a right child, enqueue it.
    • Add the current level's list to the result.
  4. Return the result.

Example Walkthrough

root
1Start BFS: enqueue root (3)
3visit920157
queue
1Queue initialized with root: [3]
Front
3
Rear
result
1result starts empty
1/6

Code