Skip to content

Binary trees

coreCommon atGoogleMetaAmazonMicrosoftRead firstRecursionStacksQueues and dequesPractise in the Sandbox →

A binary tree is a set of nodes where each node has at most a left and a right child, and every node except the root has exactly one parent. You walk it depth-first (preorder, inorder, postorder: a recursion, i.e. a stack) or breadth-first (level order: a queue). Every walk is O(n) time; depth-first uses O(height) extra space, which is O(log n) when balanced and O(n) when it degenerates into a chain.

A binary tree is a family tree where everyone has at most two kids. The words you need:

Term Meaning
root the one node with no parent
leaf a node with no children
depth of a node edges from the root down to it (root has depth 0)
height of a tree edges on the longest root-to-leaf path. Some books count nodes instead; say which you mean
subtree a node plus everything below it. It is itself a binary tree
full every node has 0 or 2 children
complete every level is full except maybe the last, which fills left to right (a heap is complete)
perfect every level is full: 2^(h+1) − 1 nodes
balanced heights of left and right subtrees differ by at most a small amount everywhere, so height is O(log n)

The key insight: a tree is recursive. A node’s left child is the root of a smaller tree. So almost every tree function has the same shape: handle the empty tree, recurse on the left, recurse on the right, combine. The only real decision is when you do the node’s own work relative to the two recursive calls. That decision is the difference between preorder, inorder and postorder.

Pick an order and step through. The highlighted path is the recursion’s call stack: the chain of visit() calls that are still waiting to finish. Switch to level order and the stack becomes a queue. Edit the tree to try a lopsided one or a chain.

Four ways to walk a binary tree
step through
1325764
call stack (top ↑)
(empty)
output
·
ruleleft subtree, then node, then right subtree
depth0
currenton the call stackoutput (#order)
inorder: left subtree, then node, then right subtree.
1 / 27

Where the three depth-first orders come from

Section titled “Where the three depth-first orders come from”

Here’s a way to see all three at once. Trace a line around the outside of the tree, hugging it. You pass every node exactly three times: on the way down (left side), between its two children (underneath), and on the way back up (right side). Preorder records the first pass, inorder the second, postorder the third.

One walk around the tree gives all three orders
step through
452631
pre (pass 1)
·
in (pass 2)
·
post (pass 3)
·
being passed nowpassed, not finishedall three passes done
Trace a line around the tree, hugging it counter-clockwise from the root. Every node gets passed three times.
1 / 20
The problem says… Traversal Why
“copy / serialize the tree”, “print the structure top-down”, “pass something from parent to child” preorder A node is handled before its subtrees, so it can hand its children information (a depth, a running path).
“in sorted order” on a binary search tree inorder Left < node < right, so left-node-right visits keys in ascending order. See BSTs.
“size / sum / height of each subtree”, “delete the tree”, “evaluate an expression tree” postorder A node needs its children’s answers before it can compute its own.
“level by level”, “nearest”, “minimum depth”, “per row” level order A queue releases all of depth d before any of depth d+1. See BFS.
  1. If the node is empty, return. This base case is what stops the recursion at every missing child.
  2. Recurse into the left subtree.
  3. Recurse into the right subtree.
  4. Do the node’s work before step 2 (preorder), between 2 and 3 (inorder), or after 3 (postorder).
traversals.py
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Node:
val: int
left: Node | None = None
right: Node | None = None
def preorder(n: Node | None, out: list[int]) -> list[int]:
if n:
out.append(n.val) # 1. node first
preorder(n.left, out) # 2. then the whole left subtree
preorder(n.right, out) # 3. then the whole right subtree
return out
def inorder(n: Node | None, out: list[int]) -> list[int]:
if n:
inorder(n.left, out)
out.append(n.val) # node between its subtrees: sorted order in a BST
inorder(n.right, out)
return out
def postorder(n: Node | None, out: list[int]) -> list[int]:
if n:
postorder(n.left, out)
postorder(n.right, out)
out.append(n.val) # node last: children are "done" before the parent
return out
if __name__ == "__main__":
# 4
# 2 6
# 1 3 5 7
t = Node(4, Node(2, Node(1), Node(3)), Node(6, Node(5), Node(7)))
assert preorder(t, []) == [4, 2, 1, 3, 6, 5, 7]
assert inorder(t, []) == [1, 2, 3, 4, 5, 6, 7]
assert postorder(t, []) == [1, 3, 2, 5, 7, 6, 4]
assert inorder(None, []) == []
print("ok")

Iterative versions: inorder with a stack, level order with a queue

Section titled “Iterative versions: inorder with a stack, level order with a queue”

Recursion hides a stack. Making it explicit avoids stack overflows on deep trees (Python’s default recursion limit is about 1000) and is a common follow-up.

  1. Inorder: walk left as far as possible, pushing every node you pass.
  2. Pop: that’s the leftmost node you haven’t output. Output it.
  3. Move to its right child and repeat from step 1.
  4. Level order: put the root in a queue. Repeatedly take from the front, output it, and add its children to the back.
iterative.py
from __future__ import annotations
from collections import deque
from dataclasses import dataclass
@dataclass
class Node:
val: int
left: Node | None = None
right: Node | None = None
def inorder_iter(root: Node | None) -> list[int]:
out: list[int] = []
stack: list[Node] = [] # the call stack, made explicit
n = root
while n or stack:
while n: # 1. dive left, remembering the way back
stack.append(n)
n = n.left
n = stack.pop() # 2. leftmost unvisited node: output it
out.append(n.val)
n = n.right # 3. then do the same for its right subtree
return out
def level_order(root: Node | None) -> list[int]:
out: list[int] = []
q = deque([root] if root else [])
while q:
n = q.popleft() # FIFO: everything on depth d leaves before depth d+1
out.append(n.val)
if n.left:
q.append(n.left)
if n.right:
q.append(n.right)
return out
if __name__ == "__main__":
t = Node(4, Node(2, Node(1), Node(3)), Node(6, Node(5), Node(7)))
assert inorder_iter(t) == [1, 2, 3, 4, 5, 6, 7]
assert level_order(t) == [4, 2, 6, 1, 3, 5, 7]
# A 100k-deep chain would blow Python's recursion limit (~1000); the loop doesn't care.
chain = None
for v in range(100_000, 0, -1):
chain = Node(v, chain)
assert inorder_iter(chain)[:3] == [100_000, 99_999, 99_998]
assert level_order(None) == []
print("ok")
Time Extra space
Any traversal O(n): each node entered once, left once
Recursive / stack-based DFS O(n) O(h), where h is the height
Level order (BFS) O(n) O(w), where w is the widest level (up to about n/2 in a perfect tree)
Balanced tree h = O(log n)
Degenerate tree (a chain) h = n − 1, so DFS uses O(n) space
Morris traversal (threaded) O(n) O(1), by temporarily rewiring null right pointers

The space trade-off between DFS and BFS flips with shape. A tall skinny tree is cheap for BFS and expensive for DFS; a short bushy tree is the reverse.

  • N-ary trees. Same traversals with a loop over children instead of left/right. Inorder only makes sense for binary trees.
  • Trees with parent pointers. You can walk upward, which turns some problems into “find where two paths meet”.
  • Trees given as arrays. Level order with null gaps (the format the visualization accepts), or the implicit 2i+1 / 2i+2 layout of a heap.
  • Rebuilding a tree. Preorder + inorder (or postorder + inorder) determine a tree with distinct values uniquely. Preorder alone does not.
  • Expression trees. Operators in internal nodes, operands in leaves. Postorder evaluates; inorder with parentheses prints. See expression evaluation.
  • Forgetting the empty tree. Decide what f(None) returns before writing anything else. It’s the base case of every recursion.
  • Confusing depth and height, or edges and nodes. A one-node tree has height 0 in edges and 1 in nodes. Ask or state your convention.
  • Recursion depth. A 100,000-node chain overflows the call stack in Python, and can in C# and JS too. Use an explicit stack if inputs can be deep.
  • queue.shift() in JavaScript is O(n). Use a head index (as in the sample) or a real deque.
  • Mixing up “balanced” and “complete” or “BST”. A binary tree has no ordering rule at all unless the problem says it’s a search tree.
  • Mutating while traversing (e.g. swapping children) without being clear whether you’ve already visited the swapped child.
  • Ask: can it be empty? can values repeat? is it a BST or just a binary tree? how deep can it get?
  • Say the recursion contract out loud: “f(node) returns the X of the subtree rooted at node. For an empty tree it’s Y. Otherwise it combines f(left) and f(right) like this.”
  • Give space as O(h) and then say what h is in the worst and balanced cases.
  • Expect follow-ups: “Do it without recursion”, “Do it level by level”, “What if the tree has a billion nodes on disk?” (streaming traversal, O(h) memory), “What if nodes have parent pointers?”
Check yourself
pick one
You need to compute the total file size of every folder in a directory tree. Which traversal order fits?
Check yourself
pick one
A recursive inorder traversal of a binary tree with n nodes is O(n) time. What is its extra space in the worst case?
Check yourself
pick one
You're given only the preorder sequence [1, 2, 3] of a binary tree with distinct values. How many different trees produce it?

These are classic textbook exercises, not gym questions:

  • Count the leaves of a binary tree, recursively and then with an explicit stack.
  • Check whether a tree is a mirror image of itself (symmetric).
  • Invert a binary tree (swap every node’s children).
  • Does any root-to-leaf path sum to a given target?
  • Rebuild a tree from its preorder and inorder sequences.
  • CLRS, ch. 12.1 (tree walks) and ch. 10.4 (representing rooted trees).
  • Sedgewick and Wayne, Algorithms §3.2, Binary Search Trees: recursive tree code done cleanly.
  • cp-algorithms: Euler tour technique, the “walk around the tree” view used above, applied to ancestor queries.
  • Knuth, The Art of Computer Programming Vol. 1, §2.3.1, “Traversing binary trees”, including threaded (Morris-style) traversal.