Binary Tree
A hierarchical structure where every node has at most two children, the foundation of BSTs, heaps and expression trees.
Definition
A binary tree is a set of nodes connected by parent-child edges where each node has at most two children, conventionally called left and right. One node, the root, has no parent; nodes with no children are leaves. The depth of a node is its distance from the root and the height of a tree is the longest root-to-leaf path.
A binary tree imposes no ordering on values by itself. Ordering constraints produce a Binary Search Tree; shape constraints produce a complete tree usable as a Binary Heap. What every binary tree shares is a naturally recursive definition: a tree is either empty or a node with two subtrees, which is why almost every tree algorithm is a few lines of recursion.
Common shape vocabulary: a full tree has 0 or 2 children per node, a complete tree is filled level by level left to right, a perfect tree has all leaves at the same depth (2^h - 1 nodes), and a balanced tree has height O(log n).
Intuition
A mental model before the formal terms.
Picture a tournament bracket turned upside down or a family tree with at most two children per person. Getting from the root to any node means following a sequence of left/right turns; in a bushy tree with a million nodes that sequence is only about 20 turns long, but in a degenerate tree where every node has one child it is a million.
Traversals are just the order in which you "visit" each person while walking the family tree: inorder visits left branch, then the person, then right branch; preorder visits the person first; postorder visits the person last, after both branches have been dealt with.
How it works
- Each node stores a value plus
leftandrightreferences (or child indices in an array-backed tree). An empty subtree isnull. - Depth-first traversals recurse: preorder (node, left, right) copies or serializes a tree; inorder (left, node, right) yields sorted order for a BST; postorder (left, right, node) computes sizes, heights and frees memory bottom-up.
- Breadth-first traversal (Breadth-First Search (BFS)) uses a Queue: push the root, then repeatedly pop a node, process it and push its children. Tracking the queue size per iteration gives level-by-level output.
- Most "compute something about a tree" problems reduce to: solve for the left subtree, solve for the right subtree, combine with the current node, return the result upward (postorder), optionally updating a global best along the way.
Why it works
The recursive definition guarantees structural induction: if an algorithm is correct on the empty tree and on a node given correct answers for both subtrees, it is correct on every finite binary tree.
Every traversal touches each node exactly once and each edge twice (down and up), so all traversals are O(n) time. Recursive DFS uses O(h) stack space; BFS uses O(w) queue space where w is the maximum level width (up to n/2).
Operations
| Operation | Description | Cost |
|---|---|---|
| traverse (DFS) | Visit every node in preorder, inorder or postorder using recursion or an explicit stack. | O(n) |
| traverse (BFS) | Visit nodes level by level using a queue. | O(n) |
| height | Longest root-to-leaf path, computed postorder as 1 + max(height(left), height(right)). | O(n) |
| search | Find a value; without ordering every node may need to be checked. | O(n) |
| insert | Attach a new node at a chosen empty slot (for level-order fill, at the first empty position found by BFS). | O(n) |
| delete | Remove a node and reattach or replace its subtrees according to the tree's policy. | O(n) |
Recognition
How to tell a problem wants this.
- The input is described as a tree, given as a root node, or a serialized level-order array like
[3,9,20,null,null,15,7]. - The question asks for depth, height, diameter, path sums, symmetry, lowest common ancestor, or "level order" output.
- A problem about nested or hierarchical data (expressions, file systems, decision paths) with at most two options per step.
Interactive demo
Play, step, change the input. ← → and space work too.
1traverse(node):2 if node is null: return3 [preorder] visit(node)4 traverse(node.left)5 [inorder] visit(node)6 traverse(node.right)7 [postorder] visit(node)8levelorder: queue = [root]9 while queue: node = queue.popleft(); visit(node); push node.left, node.rightPseudocode
1inorder(node):2 if node is null: return3 inorder(node.left)4 visit(node)5 inorder(node.right)6levelOrder(root):7 queue = [root]; while queue not empty:8 for each node in current level: visit(node); push childrenImplementation
1from collections import deque2from typing import Optional3 4 51 · Node definition6class TreeNode:7 def __init__(self, val: int) -> None:8 self.val = val9 self.left: Optional["TreeNode"] = None10 self.right: Optional["TreeNode"] = None11 12 13class BinaryTree:14 def __init__(self) -> None:15 self.root: Optional[TreeNode] = None16 172 · Level-order insert (first empty slot found by BFS)18 def insert_level_order(self, val: int) -> None:19 node = TreeNode(val)20 if self.root is None:21 self.root = node22 return23 q = deque([self.root])24 while q:25 cur = q.popleft()26 if cur.left is None:27 cur.left = node28 return29 if cur.right is None:30 cur.right = node31 return32 q.append(cur.left)33 q.append(cur.right)34 353 · Recursive DFS traversals (preorder / postorder)36 def preorder(self) -> list[int]:37 out: list[int] = []38 39 def go(n: Optional[TreeNode]) -> None:40 if n is None:41 return42 out.append(n.val)43 go(n.left)44 go(n.right)45 46 go(self.root)47 return out48 49 def postorder(self) -> list[int]:50 out: list[int] = []51 52 def go(n: Optional[TreeNode]) -> None:53 if n is None:54 return55 go(n.left)56 go(n.right)57 out.append(n.val)58 59 go(self.root)60 return out61 624 · Iterative inorder with an explicit stack63 def inorder(self) -> list[int]:64 out: list[int] = []65 stack: list[TreeNode] = []66 cur = self.root67 while cur or stack:68 while cur:69 stack.append(cur)70 cur = cur.left71 cur = stack.pop()72 out.append(cur.val)73 cur = cur.right74 return out75 765 · BFS level order77 def level_order(self) -> list[list[int]]:78 if self.root is None:79 return []80 levels: list[list[int]] = []81 q = deque([self.root])82 while q:83 level: list[int] = []84 for _ in range(len(q)):85 n = q.popleft()86 level.append(n.val)87 if n.left:88 q.append(n.left)89 if n.right:90 q.append(n.right)91 levels.append(level)92 return levels93 946 · Height (postorder combine)95 def height(self) -> int:96 def go(n: Optional[TreeNode]) -> int:97 if n is None:98 return 099 return 1 + max(go(n.left), go(n.right))100 101 return go(self.root)TreeNodestoresval,left,right; theOptional["TreeNode"]string annotation is needed because the class is referenced inside its own body.insert_level_orderusescollections.dequefor O(1)popleft().- Recursive traversals use a nested
goclosure that appends toout; Python closures can mutate the list withoutnonlocal. inorderis iterative with a list used as a stack;while cur or stackmirrors the classic algorithm.level_orderiteratesrange(len(q))to process exactly one level per outer iteration.heightreturns0forNoneand1 + max(...)otherwise.
Python's default recursion limit is 1000 frames; a skewed tree deeper than that raises RecursionError unless you use sys.setrecursionlimit or iterate.
collections.deque.popleft()is O(1);list.pop(0)is O(n).- Use
is Nonerather than truthiness for node checks to avoid surprises if a node class defines__len__or__bool__. - Type hints with
Optional[TreeNode]are documentation only; nothing is enforced at runtime. - Python has no tail-call optimisation, so deep recursion always consumes stack frames.
- Using
list.pop(0)for BFS, making level order quadratic. - Hitting
RecursionErroron deep trees; convert to the iterative form or raise the limit. - Using a mutable default argument like
def go(n, out=[]), which persists between calls.
- Memory: C++ must free nodes (destructor or
unique_ptr); JS/TS/Python are garbage collected. - Recursion limits: Python defaults to 1000 frames, JS/TS around 10k, C++ is bounded by the OS stack (~8 MB); iterative traversals are safest on skewed trees.
- Queues: C++ has
std::queue, Python hasdeque; JS/TS need an index pointer or level swap to avoid O(n)shift(). - Null: C++ uses
nullptr, JS/TSnull, PythonNone; TS makes the union explicit in the type.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(n) | O(n) | No index; must traverse. |
| Search | O(n) | O(n) | |
| Insert | O(n) | O(n) | O(1) if the attachment point is known. |
| Delete | O(n) | O(n) | |
| Update | O(n) | O(n) | O(1) once the node is found. |
| Traversal | O(n) | O(n) | |
| Height | O(n) | O(n) | |
| Space | O(n) | Recursive traversal adds O(h) stack; BFS adds O(w) queue where w is the widest level. | |
Advantages & disadvantages
- Naturally models hierarchical data and recursive structure; algorithms are short and provably correct by induction.
- Serves as the skeleton of Binary Search Tree, AVL Tree, Binary Heap, Segment Tree and expression trees.
- Traversals are linear time and need only
O(h)extra space with recursion.
- Without an ordering or balancing invariant, search is
O(n)and the tree can degenerate into a linked list. - Pointer-based nodes have poor cache locality compared with arrays.
- Deep recursion on a skewed tree of
10^5nodes overflows the default stack in most languages; an explicit stack is then required.
Use cases
- Expression trees in compilers and calculators (operators at internal nodes, operands at leaves).
- Decision trees in machine learning and game trees in search.
- Huffman coding trees (Huffman Coding) that map prefix-free codes to symbols.
- Interview problems on structure: diameter, symmetry, path sums, serialization.
- Data is inherently hierarchical with at most two branches per node (expressions, decisions).
- You need the recursive skeleton for a BST, heap, or segment tree.
- The problem hands you a tree and asks a structural question (depth, paths, symmetry, LCA).
- You need fast lookup by key with no ordering constraint — use a Hash Map.
- Nodes can have many children — use an N-ary Tree or an Adjacency List.
- Data is flat and index-addressed — an Array is simpler and cache-friendly.
Alternatives
Common mistakes
- Confusing height (edges or nodes?) — pick one convention and state it;
height(null)is0when counting nodes and-1when counting edges. - Forgetting the null check as the base case, causing a null dereference at leaves.
- Recursing on a skewed tree of
10^5nodes and overflowing the call stack; convert to an iterative traversal. - Mutating a shared result list inside recursion without resetting it between calls.
- Assuming inorder traversal yields sorted output for a plain binary tree — that only holds for a BST.
Interview patterns
- Postorder "return info up, update global best" for diameter, maximum path sum and balanced-tree checks.
- Level-order with per-level size for zigzag, right-side view and level averages.
- Serialize/deserialize with preorder plus null markers.
- Lowest common ancestor: return the node if found, otherwise whichever side found something; both sides non-null means the current node is the LCA.
- Build a tree from preorder + inorder using a hash map of inorder indices.
- Choosing between BFS, DFS, Dijkstra and DPAdvanced
- Stack versus queueBeginner
- Recursion versus iterationIntermediate
- When space complexity mattersIntermediate
- Course ScheduleIntermediate
- Network Delay TimeAdvanced
- Number of IslandsIntermediate