skip to content

Trees

Hierarchical structures are the interview's favorite recursion playground: I learn binary-tree anatomy and traversals, the BST invariant and how it breaks down, why libraries reach for self-balancing trees, and the specialist trees (tries, B-trees, segment trees) that power search engines, databases, and range queries. Interviewers use trees to test whether I can reason recursively and pick the right structure for ordered or prefix-shaped data.

part ofData structures & algorithmsoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

How does tree recursion change when a node holds a list of children instead of left and right?

level: juniorimportance: must knowfreq 72%

answer

  1. count how many recursive calls one node makes
  2. arity in the type versus arity in the data
  3. what replaces the null-child guard
  4. one for-loop over the children list
  5. child links across the whole tree total n-1

basics

~10 s

The two hardcoded recursive calls become one loop over the children list, and the null-child check becomes an empty-children-list check. Every node is still visited exactly once, so a full traversal is still O(n).

solid answer

~50 s

In a binary shape the arity is baked into the node type, so the code says `recurse(left)` then `recurse(right)` and guards each call with a null test. With a children list the arity is data, not code: one `for` loop over the node's children, recursing on each. The base case moves too — "the child pointer is null" becomes "this node's children list is empty", which usually needs no explicit test, because a loop over an empty list runs zero times. Cost is unchanged: one call per node and `n - 1` child links followed in total, so a full walk stays O(n) however wide any node is. The traversal that does not survive the generalization is inorder — with `k` children there is no canonical place to put the node among them, so n-ary trees offer preorder, postorder and level-order only.

code

pseudocode · 9 lines
pseudocode
PREORDER(node):
    VISIT(node)
    for child in CHILDREN(node):
        PREORDER(child)

// binary version, for contrast:
// VISIT(node)
// if left(node) != null: PREORDER(left(node))
// if right(node) != null: PREORDER(right(node))

go deeper

for a junior

Be ready to write the loop version of a walk on the spot and to say the base case out loud: an empty children list, not a null child. Also recall that visiting every node is O(n) whatever the fanout.

for a middle

Explain why the cost is unchanged by counting child links rather than nodes, and name inorder as the one binary traversal with no canonical n-ary meaning. Mention the reverse-push detail when converting the walk to an explicit stack.

for a senior

Show that you pick the node representation from the model's real fanout rather than defaulting to two slots, and that you keep the children order stable through every walk, since in real hierarchies that order is displayed to users.

for a principal

Own the consequence for an API: exposing children as an ordered sequence rather than named slots is what lets the model absorb new shapes without a type change, and it decides how every traversal in the codebase will be written.

## The node shape is the whole difference A binary node reserves two named slots, `left` and `right`. The arity — how many children a node may have — lives in the type itself, so the traversal code can name each child individually. An n-ary node instead holds a single collection of child references, usually in order, with no fixed upper bound. The arity has moved from the type into the data, and every piece of code that walks the tree has to follow. This is not an academic distinction. Directory trees, org charts and document element trees all have unbounded fanout: a folder can hold nine hundred entries, a manager can have five direct reports, a section element can wrap any number of children. A node type with two named slots cannot even represent those shapes, so the children list is the default representation for real hierarchical models — the binary shape is the special case, not the other way round. ## What changes in the code Three things, and only three: 1. **Two calls become a loop.** `recurse(left); recurse(right)` becomes `for child in children(node): recurse(child)`. The loop body is identical for every child; nothing about the algorithm depends on which position a child occupies. 2. **The null guard becomes an emptiness condition.** In the binary shape you must test each child pointer before recursing, because "no left child" is represented by a null slot. In the children-list shape there are no placeholder slots — a leaf simply has an empty list, so the loop runs zero times and the recursion terminates on its own. You often need no explicit base-case branch beyond guarding the initial call on an empty tree. 3. **Per-child work becomes accumulation.** Anything you did with two named results (`max(leftHeight, rightHeight)`, `leftSum + rightSum`) becomes a fold over the loop: a running maximum, a running sum, a count. A generalized preorder walk is therefore: visit the node, then loop over its children and recurse. Postorder is the same body with the visit moved after the loop. ## What does not change: the cost The common wrong answer is that unbounded fanout makes traversal more expensive — something like O(n · b) where `b` is the maximum branching factor. It does not. Count the work by edges rather than by nodes: the loop body executes once per child link in the whole tree, and a rooted tree with `n` nodes has exactly `n - 1` child links. Add one visit per node and the total is O(n). A very wide tree and a very deep tree with the same node count cost the same to walk. What the shape does change is *where* the cost of the walk lives. A deep, narrow tree pushes many nested recursive calls; a shallow, very wide tree pushes few but holds a large frontier if you traverse level by level with a queue. The node count bounds the total either way. ## What has no n-ary generalization Inorder. In a binary tree, inorder means "left subtree, node, right subtree" — the node sits in the unique gap between its two children. With `k` children there are `k + 1` gaps and no principled reason to choose one, so inorder has no canonical n-ary meaning. Preorder (node before its children), postorder (node after all its children) and level-order (tier by tier) all generalize cleanly, because each is defined relative to *the whole set* of children rather than to a specific child position. Candidates who answer "you'd do inorder by visiting the node after the first child" have invented a convention, not recalled one; the honest answer is that the ordering does not survive the generalization. ## Ordering and stability of children One subtlety worth stating out loud: the children list is *ordered*, and that order is usually meaningful — the order of entries in a listing, the order of elements in a document, the display order of reports under a manager. A traversal must preserve it, which means iterating the list front to back. If you rewrite a recursive walk as an iterative one with an explicit stack, pushing children in list order makes them pop in reverse, so you push them in reverse order to keep the original left-to-right visit sequence. That reversal is one of the most common bugs when people port binary traversal habits to n-ary shapes. ## How to answer it in an interview Say the arity moves from the type into the data; show the loop; name the base case as "empty children list" rather than "null child"; assert O(n) with the `n - 1` edges argument; and volunteer that inorder does not generalize. That is the complete answer at this level.

  • If a leaf just has an empty children list, do you need a null check anywhere at all?
    Usually only once, at the entry point, for an empty tree with no root. Inside the walk you never dereference a missing child, because you only iterate over child references that actually exist. That is the practical benefit of the list shape: the absent-child case stops being a special value you must test and becomes a list of length zero.
  • Does unbounded fanout make a full traversal asymptotically slower?
    No. The loop body runs once per child link, and a rooted tree with n nodes has exactly n-1 child links, so the total work is O(n) regardless of how wide any single node is. Branching factor affects the tree's height and the peak width of a level-order frontier, not the total number of nodes you touch.
  • If you rewrite the walk iteratively with an explicit stack, what breaks first?
    Child order. Pushing a node's children front to back makes them pop back to front, so the visit sequence silently reverses at every node. You push the children in reverse list order to recover the original left-to-right preorder — and if the children's order is meaningful, as in a document or a listing, that reversal is a real bug, not a cosmetic one.

A binary node is a form with two named boxes to fill; an n-ary node is a form with a list you keep adding rows to.

saying these in an interview costs you the question

  • Claims traversal becomes O(n times branching factor)
  • Keeps per-child null checks instead of iterating the list
  • Says inorder generalizes naturally to n-ary trees
  • Assumes each node must declare a fixed maximum child count
  • Ignores that the children list is ordered and the order matters

context

open as a page

In an AVL tree, what is a node's balance factor and which values are legal?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A node's balance factor is its left subtree's height minus its right subtree's height. An AVL tree is valid when every node's factor is -1, 0 or +1 — a bound on heights, not on node counts.

open as a page

What do a red-black tree's color invariants guarantee, and is the tree perfectly balanced?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Red-black rules — no red node has a red child, and every root-to-leaf path crosses equally many black nodes — cap height near 2·log n. That is bounded, not perfectly balanced: one branch may be twice as deep as another.

open as a page

In a binary tree, what is the difference between the height of a node and its depth?

level: juniorimportance: must knowfreq 76%

basics

~20 s

Depth counts edges from the root down to the node; height counts edges from the node down to its deepest descendant. Depth is fixed by where a node sits, while height depends on whatever hangs below it.

open as a page

In preorder, inorder and postorder traversal of a binary tree, what actually changes between them?

level: juniorimportance: must knowfreq 88%

basics

~20 s

All three walk the left subtree before the right; only the position of the node's own visit moves — before both subtree walks (preorder), between them (inorder), or after both (postorder). All three are depth-first and all cost O(n).

open as a page

Why is binary search tree lookup described as O(h) rather than O(log n)?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Lookup follows one root-to-leaf path, so its cost is proportional to the tree's height h. Height depends on insertion order: roughly log n when the tree is bushy, but as much as n when nodes form a chain.

open as a page

What does the binary search tree ordering invariant require beyond parent-child comparisons?

level: juniorimportance: must knowfreq 85%

basics

~20 s

The binary search tree invariant is subtree-wide: every key in a node's whole left subtree is smaller than that node, and every key in its right subtree is larger. Comparing a node against only its children is not enough.

open as a page

Why does a B-tree with fanout 500 beat a balanced binary tree for on-disk lookup?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Because the cost unit is page fetches, not comparisons. Each B-tree node fills one storage page, so four fetches reach tens of billions of records at fanout 500, while a balanced binary tree needs about thirty separate pointer chases.

open as a page

Why does a prefix-sum array break down when the underlying counters keep changing?

level: juniorimportance: must knowfreq 48%

basics

~20 s

A prefix-sum array stores cumulative totals, so changing one counter invalidates every prefix after it — O(n) work per write. Fenwick and segment trees store partial aggregates instead, giving O(log n) for both updates and range queries.

open as a page

In a trie, what bug appears if nodes carry no end-of-word marker?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Without an end-of-word marker a trie reports that a path of symbols exists, not that a key ended there. Insert 'carpet' and a search for 'car' walks a valid path and wrongly reports a match — as does every proper prefix.

open as a page

Why can a tree's longest node-to-node path avoid the root entirely, and how does one traversal still find it?

level: middleimportance: must knowfreq 70%

basics

~20 s

A tree's longest path has one highest node, and nothing forces that to be the root — a deep two-branched subtree beats a lopsided top. One post-order pass tracks the largest combined branch height over all nodes.

open as a page

How does finding a lowest common ancestor differ when nodes carry parent pointers versus when they do not?

level: middleimportance: must knowfreq 80%

basics

~20 s

With parent pointers, equalize the two nodes' depths, then step both upward in lockstep until they meet: O(h) time, O(1) extra space. Without them, recurse downward post-order and take the node where the two targets surface from different subtrees: O(n).

open as a page

Why must a directory-size rollup over a file tree use postorder rather than preorder?

level: middleimportance: must knowfreq 64%

basics

~20 s

A directory's total depends on its children's totals, so every child call must return before the parent can sum them. Postorder guarantees that order; preorder would have the parent aggregate values that do not exist yet.

open as a page

A binary-search-tree validator compares each node only to its immediate children — what does it wrongly accept?

level: middleimportance: must knowfreq 78%

basics

~20 s

It accepts trees that break the ordering globally: a node deep inside a left subtree can exceed the root while still satisfying its own parent. Correct validation carries an inherited (low, high) range down the recursion instead of comparing neighbours.

open as a page

Why does a single rotation fail to rebalance an AVL tree after a zig-zag insertion?

level: middleimportance: must knowfreq 62%

basics

~20 s

In a zig-zag insertion the tall subtree sits in the middle of the path, so a single rotation just hands it across and leaves a mirror-image imbalance. Two rotations — the child first, then the unbalanced node — fix it.

open as a page

How do full, complete, perfect and height-balanced binary trees differ from one another?

level: middleimportance: must knowfreq 68%

basics

~20 s

Full means every node has zero or two children. Complete means every level but the last is filled, packing left. Perfect means both, all leaves on one level. Height-balanced only bounds sibling subtree heights, so a balanced tree can look lopsided.

open as a page

Why does releasing a binary decision tree node by node require postorder, when every traversal is O(n)?

level: middleimportance: must knowfreq 72%

basics

~20 s

Releasing a node destroys the only links to its subtrees, so both children must go first — exactly postorder. All four traversals cost O(n); what differs is the direction of the dependency, which here runs from children up to the parent.

open as a page

A binary search tree's expected height is Θ(log n) under random insertion — why not rely on that?

level: middleimportance: must knowfreq 64%

basics

~10 s

Expected height assumes a uniformly random insertion order, and real key streams — timestamps, sequential identifiers, sorted exports — are not random. Self-balancing trees replace that probabilistic hope with a worst-case height guarantee.

open as a page

A binary search tree delete replaces a two-child node with its right child — what breaks?

level: middleimportance: must knowfreq 70%

basics

~20 s

Every key in the deleted node's left subtree disappears from the tree, because the returned right child never adopts it. The result is still a perfectly valid search tree, just missing keys, so lookups quietly report stored keys as absent.

open as a page

When a B-tree node overflows during insert, what happens, and how does the tree get taller?

level: middleimportance: must knowfreq 58%

basics

~20 s

A full node splits in half at its median key: the two halves become sibling nodes and the median moves up into the parent as a separator. Splits cascade upward, and only a split of the root adds a level, so every leaf stays at the same depth.

open as a page

Why does a segment tree answer an arbitrary range query in O(log n) rather than O(range length)?

level: middleimportance: must knowfreq 55%

basics

~20 s

A segment tree's nodes cover nested blocks of the index range. Any window is tiled by at most two stored nodes per level, so a query combines O(log n) precomputed aggregates instead of visiting every element.

open as a page

In a binary search tree, why must the first node whose key sits between two target keys be their lowest common ancestor?

level: juniorimportance: should knowfreq 62%

basics

~20 s

In a binary search tree, two keys stay in the same subtree only while both compare the same way against the current node. The first node that splits them is the deepest node holding both — their lowest common ancestor.

open as a page

Why must a binary tree's preorder serialization include explicit null markers to be decodable?

level: juniorimportance: should knowfreq 52%

basics

~20 s

Without markers the token stream records visit order but not which child slots are empty, so many different shapes produce the same sequence. A marker for every empty child makes each node's two slots explicit, and decoding becomes unambiguous.

open as a page

Where does a binary search tree keep its smallest key, and what does finding it cost?

level: juniorimportance: should knowfreq 55%

basics

~20 s

The smallest key sits at the leftmost node: from the root, follow left children until one has none. That walk touches one node per level, so it costs O(h), where h is the tree's height — not constant time.

open as a page

Level-order over an n-ary org chart: how do you know where one tier ends?

level: middleimportance: should knowfreq 55%

basics

~20 s

Snapshot 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.

open as a page

How does a simultaneous recursion over two binary trees decide that they are identical?

level: middleimportance: should knowfreq 58%

basics

~20 s

Walk both trees in lockstep from the roots. Both positions empty means agreement; exactly one empty means the shapes differ; otherwise the keys must match and the left pair and right pair must both agree. Cost is O(n) time and O(h) stack.

open as a page

How many rotations can a single AVL insertion trigger, and how does deletion compare?

level: middleimportance: should knowfreq 45%

basics

~20 s

One insertion needs at most one rebalancing, single or double: repairing the lowest violating node restores that subtree's pre-insert height, so no ancestor is affected. A deletion can shrink a subtree and cascade repairs up to O(log n) times.

open as a page

How does black-height prove a red-black tree's height stays within about 2·log n?

level: middleimportance: should knowfreq 48%

basics

~20 s

Every path in a red-black tree carries the same black count b, and reds cannot be adjacent, so at least half of any path is black. A subtree of black-height b holds at least 2^b − 1 nodes, giving height at most 2·log2(n+1).

open as a page

For a binary tree with n nodes, what are the minimum and maximum possible heights?

level: middleimportance: should knowfreq 58%

basics

~20 s

Counting edges, the minimum is floor(log2 n), reached when every level is packed before the next begins; the maximum is n - 1, a single chain. Being a binary tree guarantees nothing about height on its own.

open as a page

In a queue-based level-order walk of a binary tree, how do you know where each level ends?

level: middleimportance: should knowfreq 66%

basics

~20 s

Freeze the queue's size at the top of each round: those k nodes are exactly the current level. Dequeue exactly k, enqueue their children behind, and the queue then holds the next level. A sentinel marker is the alternative.

open as a page

showing 1–30 of 55