The Level Inspector
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Rackspace Technology
Problem
You're handed a binary tree decoded from a nested-dict export: each node is a dict with an int val plus left and right children that are either another such node or None. Group the values by depth, returning a list whose entry at index i holds every value at depth i in left-to-right order, with the root sitting at depth 0. An empty tree, where root is None, comes back as an empty list.
Each floor of the tower tells a different story.
Example 1
Input:
root = {"val":3,"left":{"val":9,"left":null,"right":null},"right":{"val":20,"left":{"val":15,"left":null,"right":null},"right":{"val":7,"left":null,"right":null}}}Output:
[[3],[9,20],[15,7]]
Example 2
Input:
root = null
Output:
[]
Worked solution and explanation
What this really is
Strip the costume and this is level-order traversal with the levels kept apart. Almost anyone can walk a tree breadth-first. What separates candidates is keeping each depth in its own bucket. The move that does it is to snapshot the queue length at the top of each round, because that count is exactly how many nodes sit at the current depth before you start adding their children. Miss that snapshot and your level boundaries dissolve the instant you enqueue the next row. The second trap is quieter: this tree arrives as nested dicts, so node.val throws AttributeError. It is node['val'].
Break down the requirements
Step 1: Understand the nested-dict node shape
Each node is a dict shaped {'val': int, 'left': node_or_None, 'right': node_or_None}. Read a node's value with node['val'] and reach its children with node['left'] and node['right'], where a missing child is None. Guard the empty tree: if root is None, return [] immediately so you never enqueue None.
Step 2: Use a queue seeded with the root
BFS naturally processes one level at a time. Seed the queue with the root dict to set up the first level.
Step 3: Process one full level per outer iteration
Snapshot the current queue length at the start of each iteration. That count tells you exactly how many nodes belong to the current depth before any children get added.
Step 4: Collect values left to right
As you dequeue each node-dict from the front, append node['val'] to the current level list. Then enqueue any non-None node['left'] and node['right'] for the next round.
Step 5: Append completed level to the result
Once all nodes at the current depth are processed, push the level list into the result and move on.
The solution
from collections import deque
def level_order_by_level(root):
if root is None:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
level_values = []
for _ in range(level_size):
node = queue.popleft()
level_values.append(node["val"])
if node["left"] is not None:
queue.append(node["left"])
if node["right"] is not None:
queue.append(node["right"])
result.append(level_values)
return resultCommon follow-up questions
- How would you return level order in reverse (bottom to top)? (Tests whether you can simply reverse the result list or use a stack.)
- What if you needed the zigzag (alternating left-right, right-left) order? (Tests deque manipulation or reversing alternate levels.)
- How would you find the level with the maximum sum? (Tests combining BFS traversal with aggregation logic.)
- What changes if the tree is an n-ary tree instead of binary? (Tests generalization: iterate over all children instead of just left/right.)