Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
This is plain level-order BFS plus a direction flag that flips every level. Always traverse the tree the same way (left-to-right, enqueuing children normally); only the ORDER you record each level alternates. The clean implementation collects each level into a deque and either appends to the back (left-to-right rows) or pushes to the front (right-to-left rows), so you never touch the actual traversal — just where each value lands in the row.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you separate the traversal from the output ordering — flipping how each row is recorded with a direction flag rather than altering the BFS or reversing children, which is where candidates introduce bugs.
Where candidates slip
Reversing the order children are enqueued (which scrambles subsequent levels), or reversing the row list with O(width) work each time instead of inserting at the front of a deque.
How to stand out
State 'traversal stays constant, only recording order flips', use a deque for O(1) front insertion, and note the alternative of building normally then reversing odd rows — explaining the small efficiency difference.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewBFS level (always L→R visit)leftToRight ?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2 · Optimal
Level-Order BFS with a Direction Flag and Deque Insertion (Optimal)
1Hint 1Explore hintHide hint
Strip away the zigzag for a second: this is level-order traversal. What's the minimal change to flip the order of every other level?
2Hint 2Explore hintHide hint
Keep a boolean leftToRight that toggles after each level. Don't change how you traverse — change where you place each node's value in the row.
3Hint 3Explore hintHide hint
Use a deque for the current row: when going left-to-right, add to the back; when right-to-left, add to the front. Each insertion is O(1), so no row reversal is needed.
A · Core idea
The cleanest way to think about zigzag is to realize that the traversal never changes — only the presentation does. We run exactly the same breadth-first, level-by-level sweep as ordinary level-order traversal, enqueuing each node's children left-then-right every single time. The 'zigzag' lives purely in how we record each level's values into the output.
Why a direction flag is all you need. We keep a boolean leftToRight, true for the root's level. After finishing each level we flip it. While filling a level, the flag decides where in the row the current node's value goes. On a left-to-right level we append to the back (natural order); on a right-to-left level we push to the front, so the first node visited ends up last in the row — achieving the reversal without ever changing the order in which we visit nodes.
Why a deque makes this O(1) per node. Reversing a list of width w afterward costs O(w). Instead, we collect each level into a double-ended queue: append to the back for left-to-right, appendleft (push front) for right-to-left. Front insertion on a deque is O(1), so the whole row is built in O(w) regardless of direction, with no separate reversal pass. At the end of the level we convert the deque to a plain list and add it to the result.
Why we must NOT reverse the child-enqueue order. A tempting but broken idea is to enqueue children right-then-left on alternate levels to 'naturally' flip the next row. This corrupts everything below: the order of a level's nodes determines the order their children are dequeued, so flipping enqueue order on one level scrambles the grandchildren's order in a way that compounds. By keeping enqueue order fixed (left then right, always) and isolating the flip to the recording step, the BFS frontier stays correct and only the output is serpentine.
Why the size snapshot still matters. As in any level-order BFS, we read size = queue.length before draining the level so children added during the inner loop belong to the next level. That boundary is what lets us flip the flag exactly once per level. Everything special about zigzag is layered on top of this standard, well-understood loop.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
root = [3,9,20,null,null,15,7]Interactive diagram
Zigzag over [3,9,20,null,null,15,7]
level 0 (L→R)Visiting order is constant L→R; the arrows show how the recorded row direction alternates per level.
[[3],[20,9],[15,7]]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Decouple traversal order from output order. The tree is always walked the same way; a single boolean redirects each value to the front or back of the current row. This separation — 'don't change how you explore, change how you record' — is a reusable principle for any 'same structure, different presentation' problem.
ComplexityO(n) time, O(n) space — Each node is enqueued and dequeued once, and each value is inserted into a deque in O(1) (front or back) → O(n) time. The queue holds at most one level (width w) and the output stores all n values → O(n) space overall.
E · Edge cases and pitfalls7root = [1] — One level, leftToRight stays true → [[1]].Explore edge casesHide edge cases
Edge cases handled
root = [1]One level, leftToRight stays true → [[1]].root = []Null root → return empty list immediately.root = [1,2,3,4,null,null,5]Level 1 reversed → [3,2]; level 2 left-to-right → [4,5]. Confirms enqueue order untouched while output flips.
Pitfalls
- Reversing the order children are enqueued — scrambles all deeper levels.
- Flipping the flag per node instead of per level.
- Reversing the finished row with O(width) work each odd level instead of O(1) front insertion.
- Forgetting the empty-tree guard and returning a list with a phantom empty level.
Approach 2 of 2
Level-Order BFS Then Reverse Odd Rows
1Hint 1Explore hintHide hint
What if you ignore zigzag entirely first, producing plain left-to-right level order?
2Hint 2Explore hintHide hint
Once you have the list of rows, which ones are out of order? The odd-indexed levels.
3Hint 3Explore hintHide hint
Reverse each odd-indexed row in place at the end. Simpler to reason about, though reversing costs O(width) per odd row.
A · Core idea
This variant trades a hair of efficiency for maximum clarity: first produce the ordinary left-to-right level-order traversal, then post-process by reversing every odd-indexed row.
Why it's the easiest to explain. Plain level-order BFS is a pattern most people can write without thinking — snapshot the size, drain the level into a list, enqueue children. There's no direction flag to track during traversal, so there's one fewer moving part to get wrong. The zigzag requirement is handled entirely as a separate, obvious cleanup step.
Why only odd rows are reversed. The root level (index 0) is already left-to-right, which is correct. Level 1 should be right-to-left, so it needs reversing. Level 2 is correct again, level 3 needs reversing, and so on. The rule is simply: reverse the row when its level index is odd. We can do this inside the loop (reverse before adding when the flag says so) or in a final pass over the result.
Why it's slightly less efficient than the deque version. Reversing a row of width w is O(w) work, and we do it for roughly half the levels. The total extra work is still bounded by O(n) across the whole tree, so asymptotically it's identical — but the deque-insertion approach avoids the constant-factor cost of an explicit reversal by placing each value in its final position immediately. In an interview, mentioning that you know this difference (same big-O, slightly worse constant) is a nice touch; in practice either is perfectly acceptable.
Why correctness is obvious here. Because the traversal is the unmodified, well-trusted level-order routine, the only place a bug could hide is the 'reverse odd rows' step — and that's trivially verifiable. This makes the approach a safe fallback when you want to minimize the chance of an off-by-one or an enqueue-order slip under interview pressure.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
root = [3,9,20,null,null,15,7]BFS
level order = [[3],[9,20],[15,7]]
rows collected normally[[3],[20,9],[15,7]]C · Code with teaching comments
Reference implementation previewpublic List<List<Integer>> zigzagLevelOrder(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null) return result; Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root);Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Sometimes the clearest solution is 'solve the simple version, then fix it up'. Producing canonical level order and reversing alternate rows decomposes the problem into two independently-correct pieces, which is often easier to defend than a single cleverer loop.
ComplexityO(n) time, O(n) space — Standard level-order BFS is O(n). Reversing the odd rows touches each element at most once more → still O(n) total. Output and queue are O(n) / O(w) space.
E · Edge cases and pitfalls7root = [1] — Single row, reverse stays false → [[1]].Explore edge casesHide edge cases
Edge cases handled
root = [1]Single row, reverse stays false → [[1]].Full tree of 3 levelsRows 0 and 2 untouched, row 1 reversed — verifies only odd rows flip.Wide last levelReversing the wide odd row is O(width), still within O(n) overall.
Pitfalls
- Reversing even rows by toggling the flag in the wrong phase (initialize correctly).
- Reversing while iterating, mutating the row mid-build.
- Assuming the reversal changes complexity — it stays O(n).
- Forgetting the empty-tree guard.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code preview// Definition for a binary tree node. class TreeNode { int val; TreeNode left; TreeNode right;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Flipping the child-enqueue order
Reversing which child you enqueue first to 'naturally' flip the next level scrambles the order of grandchildren — the corruption compounds down the tree. Keep enqueue order fixed.
Explore the code comparisonHide the code comparison
Flipping the child-enqueue order
Reversing which child you enqueue first to 'naturally' flip the next level scrambles the order of grandchildren — the corruption compounds down the tree. Keep enqueue order fixed.
Don't write this
if (leftToRight) {
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
} else {
if (node.right != null) queue.offer(node.right); // wrong: corrupts deeper levels
if (node.left != null) queue.offer(node.left);
}Write this instead
if (node.left != null) queue.offer(node.left); // always left then right
if (node.right != null) queue.offer(node.right);
// flip only where node.val is placed in the row2Toggling the flag per node
The direction is a property of the LEVEL, not the node. Flipping inside the inner loop alternates within a single row, producing garbage ordering.
Explore the code comparisonHide the code comparison
Toggling the flag per node
The direction is a property of the LEVEL, not the node. Flipping inside the inner loop alternates within a single row, producing garbage ordering.
Don't write this
for _ in range(size):
node = queue.popleft()
if left_to_right:
row.append(node.val)
else:
row.appendleft(node.val)
left_to_right = not left_to_right # wrong: flips per nodeWrite this instead
for _ in range(size):
node = queue.popleft()
if left_to_right:
row.append(node.val)
else:
row.appendleft(node.val)
left_to_right = not left_to_right # flip once, after the level3O(width) reversal instead of O(1) front insertion
Building a list then reversing it on every other level repeats work. A deque lets you place each value at its final end in O(1), avoiding the reversal entirely.
Explore the code comparisonHide the code comparison
O(width) reversal instead of O(1) front insertion
Building a list then reversing it on every other level repeats work. A deque lets you place each value at its final end in O(1), avoiding the reversal entirely.
Don't write this
List<Integer> row = new ArrayList<>();
for (...) row.add(node.val);
if (!leftToRight) Collections.reverse(row); // O(width) extra each odd levelWrite this instead
LinkedList<Integer> row = new LinkedList<>();
for (...) {
if (leftToRight) row.addLast(node.val);
else row.addFirst(node.val); // O(1), final position immediately
}Revise
Carry the pattern into the interview
Pattern to remember
Level-order BFS + per-level direction flag, insert front/back
Code pattern to memorize
leftToRight = true
while queue:
size = len(queue)
row = deque()
for i in range(size):
node = queue.popleft()
if leftToRight: row.append(node.val) # back
else: row.appendleft(node.val)# front
enqueue node.left, node.right (always normally)
result.append(list(row))
leftToRight = not leftToRightRules to remember
- 1Keep the traversal identical every level — only the recording order flips.
- 2Use a deque per level: append to the back on left-to-right rows, push to the front on right-to-left rows.
- 3Toggle the direction flag once per level, after finishing the row — not per node.
- 4Always enqueue children left then right; reversing enqueue order corrupts the next level's nodes.
- 5Snapshot the queue size before the inner loop, exactly as in any level-order BFS.
Use this pattern when
- Level-order output where alternate levels are reversed (serpentine / boustrophedon)
- You need rows top-to-bottom but with flipping within-row order
- Any 'snake' or 'zigzag' rendering of a tree by level
- BFS is natural and you just need to control insertion order per row
Choose another pattern when
- Plain left-to-right rows → Level Order Traversal (no flag)
- Only a per-level aggregate, not the full row → Average/Max of Levels
- Only the last node of each level → Right Side View
- Order across levels (not within) matters → that's bottom-up Level Order II
Interview takeaway
Zigzag is Level Order Traversal with one boolean. The insight that you should flip the *recording* order, never the *traversal* order, keeps the children-enqueue logic untouched and the code bug-free. Deque-front-insertion turns the reversal into an O(1)-per-node operation instead of reversing rows afterward.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternLevel-order BFS with controlled per-level output ordering. Recognise it whenever rows must alternate direction or be presented differently than visited. The lesson — decouple traversal from presentation via a flag and deque-end insertion — generalizes to any serpentine/boustrophedon rendering and to Level Order II's bottom-up flip.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Binary Tree Level Order TraversalThe base problem — drop the direction flag and always record left-to-right.
- Binary Tree Level Order Traversal IIComing soonBottom-up level order — build normally then reverse the list of levels.
- Binary Tree Right Side ViewSame BFS, but keep only the last node of each level.
- Average of Levels in Binary TreeSame level-freeze loop, reducing each level to its average.