AlgoMaster Logo

Binary Tree Level Order Traversal

Last Updated: March 29, 2026

medium
4 min read

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 just 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?

That question points directly toward BFS. But as we'll see, DFS can also solve this if we track the depth of each node.

Key Constraints:

  • Number of nodes in range [0, 2000] — With at most 2,000 nodes, even an O(n^2) approach would be fine performance-wise. But the natural solutions here are all O(n), so efficiency isn't the challenge. The real question is choosing the right traversal strategy.
  • -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

Your first instinct might be DFS since it's a tree problem, and DFS is the default tool for trees. DFS doesn't naturally process nodes level by level, but we can work around that. The trick is to carry the current depth as a parameter. Every time we visit a node, we know its level, so we can put its value into the right bucket.

Picture it this way: DFS dives deep first, but if we tag each node with its depth, we can sort the results into levels after the fact. We use a list of lists, where index 0 holds level 0's values, index 1 holds level 1's values, and so on. When we visit a node at depth d, we append its value to result[d].

The one subtlety is ordering within each level. We need left-to-right order. Since DFS visits the left subtree before the right subtree (assuming we recurse left first), and we append values as we encounter them, the left-to-right order is preserved automatically.

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

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

Code

The DFS approach works correctly but processes nodes "vertically" down branches rather than "horizontally" across levels. Is there an approach where the traversal itself naturally produces the level-by-level order?

Approach 2: BFS with Queue (Optimal)

Intuition

BFS is the textbook answer here, and for good reason. BFS uses a queue, and a queue's FIFO (first-in, first-out) property is exactly what we need. When we process a node and enqueue its children, those children go to the back of the queue. All nodes at the current level get processed before any of their children. That's level order traversal by definition.

The one piece we need to add is level separation. Plain BFS gives us nodes in the right order but doesn't tell us where one level ends and the next begins. The classic trick: at the start of each level, check the queue's current size. That size tells you exactly how many nodes belong to the current level. Process that many nodes, and everything you enqueue during that process belongs to the next level.

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

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

Code