DSAPrep
MediumTrees

Binary Tree Level Order Traversal

Given the root of a binary tree, return the level order traversal of its nodes' values (i.e., from left to right, level by level).

Example 1

Input: root = [3,9,20,null,null,15,7]
Output: [[3],[9,20],[15,7]]

Example 2

Input: root = [1]
Output: [[1]]

Example 3

Input: root = []
Output: []

Constraints

  • The number of nodes in the tree is in the range [0, 2000].
  • -1000 <= Node.val <= 1000
View original on LeetCode ↗

“Level by level” is the definition of breadth-first search — a queue naturally processes nodes in that order. The only trick is snapshotting the queue’s size before draining each level, so nodes from the next level (enqueued mid-loop) do not get mixed into the current one.

BFS (queue, level by level)

OptimalTime O(n)Space O(n)

At the start of each iteration, len(queue) is exactly the number of nodes at the current level. Popping exactly that many, and appending their children, drains one full level per outer loop iteration.

from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def levelOrder(self, root: TreeNode | None) -> list[list[int]]:
if root is None:
return []
result = []
queue = deque([root])
while queue:
level = []
for _ in range(len(queue)):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result

Tracing root = [3,9,20,null,null,15,7]:

3
9
20
15
7
1 / 3
seenresultcurrent

Queue starts with just the root. Level 0: [3].

Complexity: every node is enqueued and dequeued exactly once, doing O(1) work each time → O(n) time. The queue holds at most one full level, which can be up to ~n/2 nodes in a wide tree → O(n) space (the output array is also O(n), which does not change the bound).

DFS (recursive, tracking depth)

Time O(n)Space O(h)

BFS is not the only way: a pre-order DFS that carries its current depth can append directly into result[depth], creating a new sublist the first time a depth is reached.

class Solution:
def levelOrder(self, root: TreeNode | None) -> list[list[int]]:
result = []
def dfs(node: TreeNode | None, depth: int) -> None:
if node is None:
return
if depth == len(result):
result.append([])
result[depth].append(node.val)
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return result

Why consider this: still O(n) time, and it trades the queue’s O(n) width for the recursion stack’s O(h) depth — a win for a wide, shallow tree, though worse than BFS for a deep, narrow one. It also visits nodes in a different order (root before its children, not strictly level by level in real time), which only matters if intermediate order is observed.