Skip to content
← All posts

Average of Levels in Binary Tree: BFS Level-by-Level

5 min read
leetcodeproblemeasytreesbfs

Imagine you are a building inspector walking through a skyscraper floor by floor. On each floor, you note the temperature of every room and jot down the floor's average. You do not jump between floors randomly. You finish one floor before moving to the next. That is exactly what this problem asks you to do with a binary tree. Given the root, return the average value of the nodes on each level. This is LeetCode 637: Average of Levels in Binary Tree.

avg = 3.0avg = 14.5avg = 11.03920157Level 0Level 1Level 2sum / count per levelOutput: [3.0, 14.5, 11.0]
The tree [3, 9, 20, null, null, 15, 7] with level averages. BFS collects each level, sums the values, and divides by the count.

Why this problem matters

Average of Levels is a clean introduction to BFS level-by-level processing on trees. It reinforces the core pattern of snapshotting the queue size at the start of each level, draining exactly that many nodes, and doing something with the collected values before moving on. The "something" here is computing an average instead of collecting a list. Once you have that template down, dozens of tree problems become small variations on the same idea.

This problem also appears frequently as a warm-up in interviews because it tests whether you truly understand level-aware BFS or whether you are just memorizing solutions. If you can explain why the queue size snapshot matters, you demonstrate real understanding.

The approach

Use BFS with a queue. At the start of each level, record the current queue size. That number tells you how many nodes belong to the current level. Drain exactly that many nodes from the queue, summing their values as you go, and enqueue their children for the next level. After processing all nodes in a level, divide the sum by the count and append the result.

The key insight is the same one that powers standard level order traversal: level_size = len(queue) taken before the inner loop begins. Without that snapshot, you cannot tell where one level ends and the next begins, because children from the current level get mixed into the queue during processing.

The Python solution

from collections import deque

def average_of_levels(root):
    if not root:
        return []
    result = []
    queue = deque([root])
    while queue:
        level_size = len(queue)
        level_sum = 0
        for _ in range(level_size):
            node = queue.popleft()
            level_sum += node.val
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level_sum / level_size)
    return result

Here is what each part does:

  • queue = deque([root]) seeds the queue with the root node. Level 0 always has exactly one node.
  • level_size = len(queue) captures how many nodes are in the current level before you start dequeuing.
  • level_sum += node.val accumulates the total value for this level as you process each node.
  • level_sum / level_size computes the average once the entire level has been drained.

You do not need to collect all values into a list and then compute the average. Keeping a running sum and dividing at the end is simpler and avoids allocating an extra list per level. This is a minor optimization, but it keeps the code clean.

Step-by-step walkthrough

Let's trace BFS through the tree [3, 9, 20, null, null, 15, 7]. Watch how each level gets summed and averaged before moving to the next.

Step 1: Initialize. Push root into the queue.

3920157

Queue: [3]

Averages: []

The queue holds the root. We are about to process Level 0.

Step 2: Process Level 0. Dequeue 3, compute average.

3920157

Queue: [9, 20]

Level sum: 3 / 1 = 3.0

Averages: [3.0]

One node at this level. Sum is 3, count is 1. Average = 3.0. Children 9 and 20 enter the queue.

Step 3: Process Level 1. Dequeue 9 and 20, compute average.

3920157

Queue: [15, 7]

Level sum: (9 + 20) / 2 = 14.5

Averages: [3.0, 14.5]

Two nodes at this level. Sum is 29, count is 2. Average = 14.5. Node 9 has no children. Node 20 adds 15 and 7.

Step 4: Process Level 2. Dequeue 15 and 7, compute average.

3920157

Queue: []

Level sum: (15 + 7) / 2 = 11.0

Averages: [3.0, 14.5, 11.0]

Two leaf nodes. Sum is 22, count is 2. Average = 11.0. No children enter the queue.

Step 5: Queue is empty. Done!

3920157

Queue: []

Averages: [3.0, 14.5, 11.0]

All nodes visited. Each level contributed one average to the result list.

Notice the rhythm. At each step, you drain the entire current level from the queue, accumulate the sum, divide by the count, and push children for the next level. Once the queue is empty, every level has contributed one average to the result list.

Complexity analysis

MetricValueWhy
TimeO(n)Every node is enqueued and dequeued exactly once
SpaceO(n)The queue can hold up to n/2 nodes (the widest level of a complete binary tree)

The space is O(n) in the worst case because the bottom level of a complete binary tree contains roughly half of all nodes. For a skewed tree (a single chain), the queue never holds more than one node. The output list takes O(d) space where d is the number of levels, but d is bounded by n.

Edge cases to watch for

  • Empty tree (root is None): return an empty list []. The guard clause at the top handles this.
  • Single node (no children): return [root.val]. One level, one node, and the average is just that node's value.
  • Skewed tree (every node has only one child): each level has one node. The average of a single number is that number itself, so the result is [a, b, c, ...].
  • Large values: node values can be large integers. Summing them in Python avoids overflow since Python handles arbitrary precision, but in languages like Java or C++ you should use long or double to avoid integer overflow when summing level values.

The building blocks

This problem is built on one core pattern: BFS level-by-level processing. The template looks like this:

from collections import deque

def bfs_by_level(root):
    queue = deque([root])
    while queue:
        level_size = len(queue)
        for _ in range(level_size):
            node = queue.popleft()
            # process node
            for child in get_children(node):
                queue.append(child)

The snapshot level_size = len(queue) is what turns a plain BFS into a level-aware BFS. Once you internalize this template, you can apply it to a range of problems with minimal tweaks:

  • Level Order Traversal (LeetCode 102): collect each level into a sublist.
  • Binary Tree Right Side View (LeetCode 199): keep only the last node of each level.
  • Zigzag Level Order (LeetCode 103): reverse every other level list.
  • Average of Levels (this problem): compute the mean instead of collecting a list.

All of these are variations on the same brick. The BFS loop stays identical. Only the per-level post-processing changes.

From understanding to recall

You just read through a clean BFS solution and watched the queue drain level by level, computing averages along the way. It makes sense right now. But when you face a similar BFS-by-level problem in an interview next month, will you remember the level_size = len(queue) snapshot? Will you reconstruct the template from scratch without hesitation?

Understanding a pattern and being able to reproduce it under pressure are different skills. Spaced repetition bridges that gap. You practice typing the BFS level-by-level template from memory at increasing intervals. First after a day, then three days, then a week. Each repetition reinforces the snapshot trick, the deque usage, and the inner loop structure until it becomes automatic.

BFS level-by-level is one of roughly 60 reusable building blocks that cover hundreds of LeetCode problems. Drilling them individually with spaced repetition is far more effective than grinding random problems and hoping the patterns stick.

Related posts