At most two children per node — and the traversal toolkit (pre/in/post/level order), height, diameter, LCA, and view problems built on it.
0 of 9 lessons checked off
Introduction
What it is
A binary tree caps every node at two children — left and right, and the distinction matters. It adds no ordering rule (that's the BST's job); it's pure structure.
Its superpower is that every node splits the world in two, which makes divide-and-conquer natural and gives four canonical traversal orders.
Why it matters
Binary trees are the most-asked interview data structure after arrays: traversals, height/diameter, symmetry, lowest common ancestor, and serialization are permanent fixtures.
They're also the parent class of workhorses: BSTs (ordered), heaps (complete + ordered by priority), and expression trees (operators over operands).
How it works
DFS traversals differ only in WHEN the node is processed relative to its subtrees: pre (node first), in (between), post (after). BFS (level order) uses a queue instead of recursion.
Most problems reduce to: get answers from left and right subtrees, combine with the node, return upward. Choosing the traversal = choosing when 'combine' runs.
Where it's used
Expression trees evaluate formulas in compilers and calculators; Huffman coding trees compress files; binary space partitioning renders game scenes.
In interviews
Max depth, diameter, invert, symmetric, level-order grouping, zigzag, right/left views, LCA, serialize/deserialize, path sums — the core FAANG set.
Analogy: A tournament bracket: every match (node) feeds two earlier matches (children). Traversals are different ways to read the bracket: announce the final first (preorder) or crown it last (postorder).
Interactive diagram
Inorder shown here (Left–Root–Right); the operations below animate the others on the same tree.
Inorder traversal
Visit the left subtree, then the node, then the right subtree (Left–Root–Right). On a BST this yields sorted order.
1 / 11
Lessons in this topic
Check off lessons as you go — your progress is saved in this browser.
Binary tree structure and construction
TreeNode, left vs right, building from lists.
15 min
Preorder, inorder, postorder (recursive)
One template, three positions for the visit.
30 min
Iterative traversal with a stack
Making the call stack explicit; inorder's push-left spine.
25 min
Level-order traversal
Queue + level-size snapshots; views and zigzags.
20 min
Height, depth, and diameter
Postorder aggregation; diameter as max(left h + right h + 2).
25 min
Invert, symmetric, and same-tree
Mirroring and simultaneous recursion on two trees.
20 min
Lowest common ancestor
The 'first node where paths split' — postorder logic.
25 min
Views and vertical order
Left/right/top/bottom views from level order + column indexing.
20 min
Serialize and deserialize
Preorder with None markers round-trips any tree.
20 min
Operations
Preorder traversal (Root–Left–Right)
Process the node BEFORE its subtrees — the order that copies and serializes trees.
Preorder traversal
Visit the node first, then its left subtree, then its right subtree (Root–Left–Right). Used to copy or serialize a tree.
Visiting after the recursive calls (that's postorder).
Inorder traversal (Left–Root–Right)
Process between the subtrees. On a BST this emits values in sorted order — the single most quoted tree fact.
Inorder traversal
Visit the left subtree, then the node, then the right subtree (Left–Root–Right). On a BST this yields sorted order.
1 / 11
1definorder_iterative(root)->list[int]:2"""Explicit-stack inorder: push the left spine, pop, go right."""3out:list[int]=[]4stack:list=[]5node=root6whilenodeorstack:7whilenode:# dive down the left spine8stack.append(node)9node=node.left10node=stack.pop()# leftmost unvisited11out.append(node.val)# visit12node=node.right# then its right subtree13returnout
Time: O(n)Space: O(h)
Edge cases
BST + inorder = sorted output; use it to validate BSTs and find kth smallest.
Right-skewed trees never push — the outer loop still terminates via stack emptiness.
Both `node` and `stack` empty is the only exit.
Common mistakes
Popping before fully diving left — visits parents too early.
while stack (alone) as the loop condition — misses the initial dive.
Postorder traversal (Left–Right–Root)
Process AFTER both subtrees — the order of deletion, subtree sums, and every 'combine children's answers' problem.
Postorder traversal
Visit both subtrees before the node (Left–Right–Root). Used to delete a tree or compute subtree aggregates.
1 / 11
1defdiameter(root)->int:2"""Longest path (edges) between any two nodes — postorder in action."""3best=045defheight(node)->int:6nonlocalbest7ifnodeisNone:8return-19left=height(node.left)# children first...10right=height(node.right)11best=max(best,left+right+2)# ...then combine at the node12return1+max(left,right)1314height(root)15returnbest
Time: O(n)Space: O(h)
Edge cases
The diameter path need not pass through the root — that's why best updates at EVERY node.
Single node: diameter 0.
Return height, track diameter separately — mixing them up is the classic bug.
Common mistakes
Computing diameter as root's left height + right height only.
Recomputing height inside diameter recursively — O(n²); compute both in one postorder pass.
Level-order traversal (BFS)
A queue visits nodes distance by distance. Snapshotting the queue length gives per-level grouping — the key to views and zigzags.
Levelorder traversal
Visit nodes level by level using a queue (BFS). Used for shortest paths in unweighted structures and level grouping.
1 / 11
1fromcollectionsimportdeque234deflevel_order(root)->list[list[int]]:5"""Values grouped by level. O(n) time, O(width) space."""6ifrootisNone:7return[]8levels:list[list[int]]=[]9queue=deque([root])10whilequeue:11level_size=len(queue)# snapshot BEFORE the loop12level:list[int]=[]13for_inrange(level_size):14node=queue.popleft()15level.append(node.val)16ifnode.left:17queue.append(node.left)18ifnode.right:19queue.append(node.right)20levels.append(level)21returnlevels
Time: O(n)Space: O(max width) — up to n/2 for the last level
Edge cases
Empty tree returns [] not [[]].
Right view = last element of each level; left view = first.
Zigzag = reverse alternate levels after collection.
Common mistakes
Reading len(queue) inside the for condition while appending children — the level boundary dissolves.
Using a list with pop(0) as the queue: O(n²).
Lowest common ancestor
Recurse both sides; a node that sees the targets in different subtrees (or is itself one of them) is the answer.
Lowest common ancestor
1deflowest_common_ancestor(root,p,q):2"""LCA in a plain binary tree. O(n) time, O(h) space."""3ifrootisNoneorrootisporrootisq:4returnroot5left=lowest_common_ancestor(root.left,p,q)6right=lowest_common_ancestor(root.right,p,q)7ifleftandright:# p and q split here — this is the LCA8returnroot9returnleftorright# bubble up whichever side found something
Time: O(n)Space: O(h)
Edge cases
A node is its own ancestor: LCA(p, p's descendant) = p — handled by the early return.
Assumes both nodes exist in the tree; say so, or verify existence first.
Compare node IDENTITY, not values (duplicates).
Common mistakes
Returning values instead of nodes and losing identity.
Trying to collect root-to-node paths first — works (O(n) extra space) but the direct recursion is the expected answer.
Complexity analysis
Operation
Best
Average
Worst
Space
Any full traversal (DFS/BFS)
O(n)
O(n)
O(n)
O(h) / O(width)
Height / diameter / symmetric
O(n)
O(n)
O(n)
O(h)
Search (no ordering)
O(1)
O(n)
O(n)
O(h)
LCA (single query)
O(n)
O(n)
O(n)
O(h)
Serialize / deserialize
O(n)
O(n)
O(n)
O(n)
Plain binary trees have no order, so anything 'find'-shaped is O(n). Ordering (BST) or shape guarantees (heap) are what buy speed.
Python implementation
Production-quality code with type hints, validation, and docstrings.
Serialize and deserialize (preorder + None markers)
1fromtypingimportOptional234classTreeNode:5def__init__(self,val:int=0,left=None,right=None):6self.val,self.left,self.right=val,left,right789defserialize(root:Optional[TreeNode])->str:10"""Preorder with '#' for None — uniquely encodes the structure."""11parts:list[str]=[]1213defwalk(node:Optional[TreeNode])->None:14ifnodeisNone:15parts.append("#")16return17parts.append(str(node.val))18walk(node.left)
What interviewers expect you to know
What interviewers expect you to know
All four traversals, recursive AND iterative-inorder, without warm-up.
The postorder pattern: return info upward, combine at the node — height, diameter, balanced-check, path sums all reuse it.
Level-size snapshotting for anything phrased 'per level' or 'view'.
BST inorder = sorted — even on the plain binary-tree page, because follow-ups pivot on it.
Classic follow-ups
"Do it iteratively" (after any recursive traversal) — the stack version, and knowing WHY (recursion limits, explicit control).
"O(1) space traversal?" — Morris traversal exists via threaded trees; name it, don't derive it.
"Diameter in one pass?" — height recursion with a nonlocal best; avoid the O(n²) recompute.
"Which traversals reconstruct a tree?" — in+pre, in+post (unique with distinct values); pre+post alone is ambiguous.
How to talk through tree problems
Open with the contract: 'my function returns the height of this subtree' — then the combine step writes itself.
Name the traversal you're choosing and why: 'per-level output → BFS with size snapshots'.
Common mistakes
Height vs depth confusion
Diameter, balance checks and 'max depth' all break if you measure from the wrong end. Depth: from root. Height: to leaves.
The O(n²) diameter
Calling height() inside a separate diameter() recursion recomputes heights at every node. Fold both into one postorder pass.
Level boundaries dissolved
BFS without snapshotting len(queue) merges levels — right view and zigzag silently return wrong answers.
Wrong base case
Returning 0 for None when the convention needs −1 (edge-counting height) shifts every answer by one.
Identity vs equality
LCA and same-tree problems compare nodes with ==; duplicates then lie. Use `is` for node identity.
Missing the None check first
Every recursive tree function starts `if root is None:` — skipping it crashes on leaves' children.
Practice problems
Hand-picked from the 355-problem DSA Atlas. Reveal hints and solutions progressively; mark and bookmark as you solve.
Commonly associated with: Amazon, Meta, Google, Microsoft
O(n) time · O(n) space
Topic quiz
6 questions across concepts, code output, complexity, and scenarios. Answer all, then submit for explanations.
Frequently asked questions
How do I choose between DFS and BFS on a tree?
Phrased per-level, by distance, or 'view' → BFS. Aggregating subtree information (heights, sums, balance) → DFS postorder. Path-from-root problems → DFS preorder carrying state down.
Which two traversals uniquely rebuild a tree?
Inorder + preorder, or inorder + postorder (with distinct values). Preorder + postorder alone is ambiguous for single-child nodes. Alternatively, ONE traversal with explicit None markers suffices — that's serialization.
Is Morris traversal worth learning?
Know it exists: inorder in O(1) space by temporarily threading right pointers. Interviews rarely require implementation; naming the trade-off (mutates the tree during traversal) is usually full credit.
Summary & cheat sheet
Key takeaways
Traversal choice = when the node is processed: pre (before), in (between), post (after), level (by distance).
Postorder aggregation — return child answers, combine at the node — solves the height/diameter/balance family.
BFS + len(queue) snapshot = per-level everything.
LCA: the node where the two searches split.
Preorder + None markers serializes any binary tree uniquely.