Reasoning Into CodeArunank Divya Sharan · Python edition← Arunank’s portfolio
Module 18 · Tree BFS, Binary Search Trees, and Tries

171. Binary Tree Level Order

Foundation · Download this solution · All solutions and shared helpers

Problem

Return a binary tree's values grouped by depth, with each level ordered left to right. Empty input returns []. Preserve repeated values as distinct node occurrences and do not mutate the tree.

Examples

Example 1

level_order(build_tree([1,2,3,4,5]))
=> [[1], [2, 3], [4, 5]]

Captured queue boundaries separate three depths.

Example 2

level_order(None)
=> []

There are no levels.

Example 3

level_order(build_tree([7]))
=> [[7]]

The root forms one complete level.

Reveal the worked solution and reasoning

Why this mechanism works

A FIFO queue already visits nodes in depth order, but grouping requires an explicit boundary. Capture the queue length before processing a level. Children appended during that loop belong to the next level and must wait until the next outer iteration.

State and boundaries

At each outer-loop start the queue contains exactly the next level, left to right. level holds the current layer's emitted values.

Protocol

  1. Return [] for None.
  2. Enqueue the root.
  3. Capture the current queue length.
  4. Remove exactly that many nodes, recording values and enqueuing left then right children.
  5. Append the completed level.

Visualization

queue [1] -> level [1], next queue [2,3]
capture 2 -> level [2,3], next queue [4,5]
new children wait for the next outer loop

Python implementation

from collections import deque
from structures import build_tree

def level_order(root):
    if root is None:
        return []
    queue, output = deque([root]), []
    while queue:
        level = []
        width = len(queue)
        for _ in range(width):
            node = queue.popleft()
            level.append(node.val)
            if node.left is not None:
                queue.append(node.left)
            if node.right is not None:
                queue.append(node.right)
        output.append(level)
    return output

Worked execution

Recorded state changes after statements; L numbers refer to the solution file. Long traces are explicitly condensed.

level_order(build_tree([1,2,3,4,5]))

L5 level_order: if root is None:
root=N1(val=1,left=N2,right=N3)

L7 level_order: queue, output = deque([root]), []
root=N1(val=1,left=N2,right=N3); queue=deque([N1(val=1,left=N2,right=N3)]); output=[]

L9 level_order: level = []
root=N1(val=1,left=N2,right=N3); queue=deque([N1(val=1,left=N2,right=N3)]); output=[]; level=[]

L10 level_order: width = len(queue)
root=N1(val=1,left=N2,right=N3); queue=deque([N1(val=1,left=N2,right=N3)]); output=[]; level=[]; width=1

L11 level_order: for _ in range(width):
root=N1(val=1,left=N2,right=N3); queue=deque([N1(val=1,left=N2,right=N3)]); output=[]; level=[]; width=1; _=0

L12 level_order: node = queue.popleft()
root=N1(val=1,left=N2,right=N3); queue=deque([]); output=[]; level=[]; width=1; _=0; node=N1(val=1,left=N2,right=N3)

L13 level_order: level.append(node.val)
root=N1(val=1,left=N2,right=N3); queue=deque([]); output=[]; level=[1]; width=1; _=0; node=N1(val=1,left=N2,right=N3)

L15 level_order: queue.append(node.left)
root=N1(val=1,left=N2,right=N3); queue=deque([N2(val=2,left=N4,right=N5)]); output=[]; level=[1]; width=1; _=0; node=N1(val=1,left=N2,right=N3)

... 16 intermediate state changes omitted ...

L11 level_order: for _ in range(width):
root=N1(val=1,left=N2,right=N3); queue=deque([N5(val=5,left=None,right=None)]); output=[[1], [2, 3]]; level=[4]; width=2; _=1; node=N4(val=4,left=None,right=None)

L12 level_order: node = queue.popleft()
root=N1(val=1,left=N2,right=N3); queue=deque([]); output=[[1], [2, 3]]; level=[4]; width=2; _=1; node=N5(val=5,left=None,right=None)

L13 level_order: level.append(node.val)
root=N1(val=1,left=N2,right=N3); queue=deque([]); output=[[1], [2, 3]]; level=[4, 5]; width=2; _=1; node=N5(val=5,left=None,right=None)

L18 level_order: output.append(level)
root=N1(val=1,left=N2,right=N3); queue=deque([]); output=[[1], [2, 3], [4, 5]]; level=[4, 5]; width=2; _=1; node=N5(val=5,left=None,right=None)

Result: [[1], [2, 3], [4, 5]]. Captured queue boundaries separate three depths.

level_order(None)

L5 level_order: if root is None:
root=None

Result: []. There are no levels.

Why the solution is correct

Initially the queue is the root level. Processing exactly that prefix emits the whole current level; appending its children in parent order creates the next level in left-to-right order. Induction over depth proves every group correct. Every node enters and leaves once.

Time and space

O(n) time, O(n) output and O(w) queue space, where w is maximum level width.

Common mistakes

Using a changing queue length as the level limit can consume newly appended children in the same group. A list pop(0) adds linear shifting cost.

Reasoning card

State: At each outer-loop start the queue contains exactly the next level, left to right. level holds the current layer's emitted values.

Invariant: The current queue prefix contains exactly one tree level.

Decision to rehearse: Capture queue length before each level.

Finish: Append the completed level

Transfer exercise

Return each level's average while keeping only its sum and node count.