Level-order over an n-ary org chart: how do you know where one tier ends?
answer
- the queue is not empty between tiers
- you need a boundary the queue does not give
- what is true of the queue before a round starts
- read the count before popping, not during
- size snapshot, then pop exactly that many
basics
~20 sSnapshot the queue's size before each round and dequeue exactly that many nodes; those are one full tier, and whatever they enqueue is the next tier. The queue never empties between tiers, so emptiness cannot mark the boundary.
solid answer
~50 sLevel-order uses a queue seeded with the root. To render tier by tier you need a boundary, and the queue does not give you one for free — while you are draining a tier you are simultaneously enqueuing the next, so the queue is almost never empty at a tier edge. The standard fix is a count: read `size = length(queue)` *before* the round starts, pop exactly `size` nodes, and enqueue each popped node's entire children list. When the round ends, the queue holds precisely the next tier. Alternatives are to enqueue `(node, depth)` pairs and start a new tier when the popped depth changes, or to push a sentinel between tiers. The children list is what makes this work at all: a node type with two fixed slots cannot represent a manager with five reports, so "enqueue all children" would not be expressible. Time is O(n); the queue's peak size is the widest tier.
go deeper
Be ready to describe the queue mechanic: seed with the root, pop a node, enqueue all of its children, repeat. Know that the queue does not empty between tiers, so the boundary needs explicit bookkeeping.
State the invariant that the queue holds exactly one tier at the start of each round, and explain why the size must be read before popping. Offer depth-tagged entries or a two-list swap as equivalent alternatives.
Bring the operational angle: time is linear but peak memory is the widest tier, so a wide shallow chart argues for streaming each tier out as it drains rather than materialising the whole rendering first.
Own the modelling decision behind it — an ordered children collection on the node is what lets reporting lines, listings and menus share one traversal, and locking the node type to two slots forces a rewrite the first time a real hierarchy arrives.
## Why the boundary needs bookkeeping A level-order walk keeps a queue, pops a node, enqueues that node's children, and repeats until the queue is empty. That produces the right *sequence* of nodes — every node at depth `d` before any node at depth `d + 1` — but it produces a flat stream, and rendering an org chart tier by tier needs the stream cut into groups. The naive instinct, "the queue empties between levels", is wrong: at the moment you pop the last node of a tier, that tier's earlier nodes have already enqueued their reports, so the queue is full of the *next* tier. The queue is empty exactly once, at the very end. ## The size-snapshot technique The invariant that makes it work: **at the top of each round, the queue contains exactly the nodes of one tier, in order.** Establish it by seeding the queue with the root alone. Maintain it like this: 1. Let `size = length(queue)` — read once, before popping anything. 2. Repeat `size` times: pop a node, emit it into the current tier's output, and enqueue every entry of its children list in order. 3. Nothing popped in this round was enqueued in this round, because you fixed `size` up front. So after the round the queue contains exactly the children of the whole tier — the next tier, in order. 4. Stop when the queue is empty. The single most common bug is reading the queue's length *inside* the loop instead of before it: the bound then grows as children are added, and the round swallows part of the next tier. A second common bug is enqueuing only the first child, which comes from binary habits and quietly drops most of the chart. ## The representation point This leaf's real lesson hides in step 2: "enqueue every entry of its children list". If the node type declares two fixed slots, that sentence has no meaning — a manager with five direct reports cannot be represented at all, let alone traversed. Hierarchies people actually render (reporting lines, directory listings, document element trees, category menus) have unbounded fanout, so the node carries an ordered collection of children and the traversal loops over it. The binary shape is not a simplification of these models; it is a different model that cannot express them. When an interviewer hands you a node type with `left` and `right` and asks you to render an org chart, the first correct move is to change the node type. Order matters too. The children list is ordered, and that order is usually the display order. Level-order preserves it as long as each node enqueues its children front to back — a queue is first-in-first-out, so enqueue order is emit order within the next tier. ## Alternatives to the count - **Depth-tagged entries.** Enqueue `(node, depth)` pairs, or store a depth field when enqueuing. Start a new tier whenever the popped depth differs from the previous one. This costs a little extra memory per queued entry but hands you the tier number directly, which is convenient when the renderer needs indentation levels. - **Sentinel markers.** Push a marker after the root, and each time you pop the marker, close the current tier and push a new marker if the queue is non-empty. Compact, but easy to get wrong at the end, where pushing a final marker into an empty queue loops forever. - **Two lists instead of a queue.** Keep the current tier as a list, build the next tier by walking it and collecting all children, then swap. This is often the clearest version when the output *is* tiers, and it makes the invariant self-evident because each list literally is a tier. It also parallelises naturally, since the nodes of one tier are independent. All four are O(n) time. Memory differs only in constants; the dominant term in every version is the widest tier, because at the boundary you are holding a full tier of node references. ## Cost, honestly stated Time is O(n): each node is enqueued once and popped once, and the enqueue loops run once per child link, `n - 1` in total. Peak auxiliary memory is proportional to the maximum tier width. That is the interesting asymmetry with depth-first walks: a wide, shallow chart is cheap to recurse over and expensive to hold a tier of, while a deep, narrow chain is the reverse. For an org chart — typically shallow and very wide at the middle tiers — the widest tier is the number that matters, and it is worth saying out loud that rendering a hundred-thousand-person chart tier by tier means materialising the widest tier in memory at once, which is an argument for streaming a tier out as it is drained rather than collecting all tiers first. ## What a strong answer sounds like Name the invariant ("the queue holds exactly one tier at the top of each round"), give the snapshot mechanic, mention the read-size-inside-the-loop bug, note that the node needs a children list for any of it to work, and close with O(n) time and widest-tier memory.
- What happens to the tier count when a manager has no direct reports?Nothing special. A leaf enqueues zero children, so it contributes nothing to the next round's size. The next tier's count is simply whatever the whole current tier enqueued, and if that is zero the queue is empty and the walk ends. No separate leaf case is needed.
- How would you attach a tier number to each node without counting per round?Enqueue the node together with its depth, or record a depth when enqueuing, and start a new tier whenever the popped depth differs from the previous one. The output is identical; you trade a small per-entry memory cost for having the tier number in hand, which suits a renderer that needs indentation.
- What dominates memory when rendering a very wide chart this way?The widest tier. At a tier boundary the queue holds every node of one tier at once, so peak auxiliary memory is proportional to maximum width, not to height or to total node count. On a shallow, very wide chart that is the constraint worth designing around — for instance by streaming each tier out as it drains instead of accumulating all tiers.
saying these in an interview costs you the question
- Assumes the queue is empty between levels
- Reads the queue's size inside the round after popping
- Keeps two fixed child slots for a manager with five reports
- Enqueues only the first child of each node
- Re-walks the whole tree once per tier to find that tier's nodes