Back to Trees & BST
Q10Question 10 of 25

Binary Tree Level Order Traversal (BFS Level by Level)

Key Concepts
  • Level-size snapshot: capture queue.size() before draining to isolate each level.
  • Drain exactly levelSize nodes, enqueuing their children for the next level.
  • Result is a list of lists, each inner list holding one level's values left to right.
  • The snapshot is the reusable core for all Tree BFS variants (zigzag, right view, min depth).
  • Time O(n), space O(w) where w is the maximum width of the tree.
  • BFS space is O(width); DFS space is O(height); choose based on tree shape.

Definition

Use a queue, snapshot the level size, drain exactly that many nodes while enqueuing their children, producing one list per level.
  • Tree BFS pattern – visit nodes level by level using a FIFO queue.
  • Level-size snapshot – at the start of each level, record queue.size() to know how many nodes belong to this level.
  • Drain and enqueue – dequeue exactly that many nodes, collect their values, and enqueue their non-null children.
  • Result – a list of lists, each inner list holding one level's values left to right.
  • Complexity – O(n) time, O(w) space where w is the maximum width of the tree.

The National Bank of India prints its approval hierarchy rank by rank. The branch manager is at the top, department heads on the next rank, then clerks. To list staff rank by rank, you start with the manager in a queue, process them, add their direct reports to the back of the queue, and keep processing one full rank at a time.

Understanding the Concepts

What is Level Order Traversal?

Level order traversal visits all nodes on one level before moving to the next level. It goes from top to bottom, and within each level from left to right. The result is a list of lists, where each inner list contains the values of one level.

This is the Tree Breadth First Search (BFS) pattern, and it is the foundation for many tree BFS variants.

How does the level-size snapshot work?

The essential trick is capturing levelSize = queue.size() at the start of each level. This snapshot tells you exactly how many nodes belong to the current level. You drain precisely that many nodes, enqueuing each node's children. Everything enqueued during that drain belongs to the next level.

JAVA
package com.bank.service;

import java.util.*;

public class HierarchyLevelPrinter {

    static class TreeNode {
        int approvalAmount;
        TreeNode left, right;
        TreeNode(int val) { this.approvalAmount = val; }
    }

    public static List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            int levelSize = queue.size(); // snapshot
            List<Integer> level = new ArrayList<>();

            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                level.add(node.approvalAmount);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            result.add(level);
        }
        return result;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1000);
        root.left = new TreeNode(500);
        root.right = new TreeNode(800);
        root.left.left = new TreeNode(200);
        root.left.right = new TreeNode(300);
        root.right.right = new TreeNode(400);

        System.out.println("Level order: " + levelOrder(root));
    }
}
TEXT
Level order: [[1000], [500, 800], [200, 300, 400]]

What variants does the Tree BFS pattern support?

The level-size snapshot is the reusable core that every Tree BFS variant builds on.

| Variant | Modification |
|---|---|
| Reverse level order | Add each level at the front of the result list |
| Zigzag level order | Flip direction flag per level |
| Right side view | Take only the last node of each level |
| Left side view | Take only the first node of each level |
| Level averages | Compute the average of each level |
| Minimum depth | Return depth at first leaf encountered |
| Maximum depth | Count total levels processed |
| Connect siblings | Wire next pointers within each level |

JAVA
// Reverse level order: add level at front
result.add(0, level); // instead of result.add(level)

// Right side view: take last element
if (i == levelSize - 1) rightView.add(node.approvalAmount);

How is BFS different from DFS for trees?

| Aspect | BFS (Level Order) | DFS (In/Pre/Post) |
|---|---|---|
| Data structure | Queue | Stack (or recursion) |
| Visit order | Level by level | Deep before wide |
| Space | O(width) | O(height) |
| Best for | Level grouping, shortest path | Sorted output, path problems |
| Balanced tree space | O(n/2) at bottom level | O(log n) |
| Degenerate tree space | O(1) single-width | O(n) full depth |

For a balanced tree, BFS uses more memory because the bottom level holds about n/2 nodes. For a deep narrow tree, BFS uses less. The choice depends on the tree shape and the problem.

JAVA
// DFS uses O(height) stack space
public static void dfs(TreeNode node) {
    if (node == null) return;
    System.out.println(node.approvalAmount);
    dfs(node.left);
    dfs(node.right);
}
// Space: O(log n) balanced, O(n) degenerate

What are the edge cases?

  • Null root – return empty list.
  • Single node – return [[root.val]].
  • Left-skewed tree – each level has one node; result has n inner lists of size 1.
  • Perfect binary tree – bottom level has n/2 nodes, which is the peak queue size.
JAVA
// Edge case: null root
System.out.println(levelOrder(null)); // []

// Edge case: single node
TreeNode single = new TreeNode(42);
System.out.println(levelOrder(single)); // [[42]]

What are common mistakes?

The most common mistake is forgetting to snapshot the level size, causing the inner loop to process nodes from multiple levels.

JAVA
// WRONG: no level-size snapshot
while (!queue.isEmpty()) {
    TreeNode node = queue.poll();
    // This processes one node at a time, losing level boundaries
}

// CORRECT: snapshot level size
while (!queue.isEmpty()) {
    int levelSize = queue.size(); // snapshot before draining
    for (int i = 0; i < levelSize; i++) {
        TreeNode node = queue.poll();
        // process
    }
}

What is the time and space complexity?

Time is O(n) because every node is enqueued and dequeued exactly once. Space is O(w) for the queue, where w is the maximum width. For a balanced tree, w is about n/2 at the bottom level. The result list uses O(n) for all values.

What should a senior engineer know?

A senior engineer should know that the level-size snapshot is the universal building block for all Tree BFS problems. Every variant (zigzag, right view, connect siblings, min/max depth) uses the same queue loop with the same snapshot. The modification is always in how each dequeued node's value is used, not in the traversal itself.

They should also understand the space trade-off with DFS: for wide trees BFS uses more memory, for deep trees DFS uses more. In production systems with very wide trees (like B-trees), BFS queues can become large, and iterative deepening DFS can be a memory-efficient alternative.

Visual Overview

DIAGRAM
  Tree:
           1000
          /    \
        500    800
       /   \      \
     200   300    400

  BFS Queue Trace:
  ┌──────────────────────────────────────────────┐
  │ Level 0: queue = [1000]                      │
  │   size=1, drain: 1000                        │
  │   enqueue: 500, 800                          │
  │   result: [[1000]]                           │
  ├──────────────────────────────────────────────┤
  │ Level 1: queue = [500, 800]                  │
  │   size=2, drain: 500, 800                    │
  │   enqueue: 200, 300, 400                     │
  │   result: [[1000], [500, 800]]               │
  ├──────────────────────────────────────────────┤
  │ Level 2: queue = [200, 300, 400]             │
  │   size=3, drain: 200, 300, 400               │
  │   enqueue: (none)                            │
  │   result: [[1000], [500, 800], [200,300,400]]│
  └──────────────────────────────────────────────┘

  Level-Size Snapshot:
  ┌────────────────────────────────────┐
  │ levelSize = queue.size()           │
  │ Captures exactly how many nodes    │
  │ belong to the CURRENT level before │
  │ children are added for next level. │
  └────────────────────────────────────┘

Key Takeaway

Binary tree level order traversal is the foundation of the Tree Breadth First Search pattern. It uses a FIFO queue plus the level-size snapshot: at the start of each level, capture the current queue size, drain exactly that many nodes while enqueuing their children, and collect each level's values into its own list. This cleanly separates levels without depth markers.

The level-size snapshot is the reusable core that every Tree BFS variant builds on. Zigzag traversal flips direction per level. Right-side view takes the last node per level. Minimum depth stops at the first leaf. Maximum depth counts levels. Connect siblings wires next pointers. All use the same queue loop.

At the National Bank of India, this rendered the approval hierarchy rank by rank. The branch head sat at the root, department heads on level 1, and officers on level 2. Processing the tree with a queue and grouping by the level-size snapshot produced each rank's approvers for the audit report.

The senior trade-off is BFS versus DFS space. BFS uses space proportional to the widest level (O(n/2) for balanced trees), while DFS uses space proportional to height (O(log n) balanced, O(n) degenerate). For wide trees, DFS may be lighter. Understanding this trade-off and articulating that the level-size snapshot is the universal BFS building block is what signals real understanding.

Remember one sentence: Snapshot queue.size() at each level's start, drain that many nodes while enqueuing children, and every Tree BFS variant is just a modification of how each dequeued value is used.

---

Share
Use ← → arrow keys to navigate