LearnAbout

Binary Trees

On this page
Quick reference
Preorder / inorder / postorder (DFS)O(n)
Level-order (BFS)O(n)
height(node)O(n)
The Idea

Every structure so far has been a straight line. An array is a row of lockers. A linked list is a scavenger hunt, one clue leading to exactly one next clue. A stack and a queue are both just a line with rules about which end you touch. A tree is the first structure that branches: a node doesn't point to one next thing, it can point to two.

Think of a family tree, or a company org chart. One person sits at the top with no boss above them. Each person can have up to two people reporting directly below them. From any one person, you can only see their own two direct reports โ€” not the whole chart at once โ€” and to reach someone three levels down you have to go through whoever sits between you and them, one hop at a time.

A binary tree is exactly that shape, formalized. Each node holds a value and two pointers, left and right, each either pointing to another node or to nothing. The one node nobody points to is the root โ€” the top of the chart. A node whose left and right are both empty is a leaf โ€” the bottom of some branch, nobody reporting to them.

A linked list node has one next. A binary tree node has two, left and right โ€” that single change, one pointer becoming two, is what turns a straight line into a shape that branches.
Build It
The node
structure Node:
    value
    left          // -> another Node, or NULL
    right         // -> another Node, or NULL

// the tree itself is just a reference to its root node โ€” a NULL root is an empty tree
preorder traversal โ€” root, left, right
function preorder(node, result):
    if node is NULL:                // base case โ€” nothing here to visit
        return
    result.append(node.value)       // visit the root before either subtree
    preorder(node.left, result)
    preorder(node.right, result)
Straight out of Recursion Basics: a base case (null node, do nothing) and a recursive case (visit, then trust the left subtree and the right subtree to handle themselves). Nothing about a tree changes that shape โ€” it just makes two recursive calls instead of one.
inorder traversal โ€” left, root, right
function inorder(node, result):
    if node is NULL:
        return
    inorder(node.left, result)
    result.append(node.value)       // visit the root between the two subtrees
    inorder(node.right, result)
Same three lines, root moved to the middle. This ordering looks arbitrary on a plain binary tree โ€” but the next topic adds one rule to this structure that makes inorder traversal come out in sorted order for free.
postorder traversal โ€” left, right, root
function postorder(node, result):
    if node is NULL:
        return
    postorder(node.left, result)
    postorder(node.right, result)
    result.append(node.value)       // visit the root last, after both subtrees are done
level-order traversal (BFS) โ€” one level at a time, using a queue
function levelOrder(root):
    result = []
    if root is NULL:
        return result
    queue = Queue()
    enqueue(queue, root)
    while queue is not empty:
        node = dequeue(queue)
        result.append(node.value)
        if node.left is not NULL:
            enqueue(queue, node.left)
        if node.right is not NULL:
            enqueue(queue, node.right)
    return result
This is exactly why Phase 3 taught queues. A stack would visit depth-first and give you the wrong order entirely โ€” enqueue a node's children and they wait in line behind everyone else discovered at the same level, so the queue naturally drains one whole level before starting the next.
height โ€” 1 + the taller of the two subtrees
function height(node):
    if node is NULL:                            // base case โ€” an empty subtree has height 0
        return 0
    return 1 + max(height(node.left), height(node.right))
Know It

Every traversal touches each node exactly once, so time is O(n) across the board โ€” preorder, inorder, postorder, level-order, height, all O(n). Space is where they split. The three DFS traversals recurse, so their space cost is the call stack depth: O(h), where h is the tree's height โ€” recall Recursion Basics, a pending call sits in memory until it returns. BFS never recurses, but its queue can hold an entire level at once.

OperationTimeSpaceWhy
Preorder / inorder / postorder (DFS)O(n)O(h)Every node is visited once โ€” O(n) time. The recursion stack holds one frame per level of depth currently being explored, so its size is the tree's height h: O(log n) if the tree is balanced, O(n) worst case if it's a totally skewed chain.
Level-order (BFS)O(n)O(n)Every node is enqueued and dequeued once โ€” O(n) time. But the queue can hold an entire level at once, and the widest level of a balanced tree can hold up to roughly n/2 nodes โ€” so queue space is O(n) worst case, not O(h).
height(node)O(n)O(h)Same shape as any DFS traversal: visits every node once, and the recursion never goes deeper than the tree's own height.
Break It

Empty tree

root is NULL

Every function above starts with the same check. preorder/inorder/postorder on a NULL node just return without appending anything; levelOrder returns an empty list before ever touching a queue; height(NULL) returns 0. Skip this check anywhere and you dereference a node that was never there.

Single node

root exists but root.left and root.right are both NULL

The root is a leaf at the same time โ€” both descriptions are true of the exact same node. All three DFS orders visit it once and stop; levelOrder enqueues it, dequeues it, finds no children to enqueue, and the queue is empty on the next check. height returns 1, not 0 โ€” the base case (NULL) is 0, but a real node, even a childless one, adds one level on top of that.

Skewed tree โ€” every node has only one child

each node points only left (or only right), never both

Nothing stops a binary tree from degenerating into the exact shape of a linked list โ€” one long chain, n nodes deep, zero branching. height(node) then returns n instead of logโ‚‚(n): the O(h) space bound from Know It becomes O(n) in the worst case, not O(log n). This matters more than it looks like it should โ€” the next topic builds a structure directly on top of binary trees where this exact shape is the main failure mode to watch for.

Mixing up the three DFS orders

reading off preorder, inorder, and postorder for the same small tree

Take the 3-node tree with root 2, left child 1, right child 3. Preorder (root, left, right) reads 2, 1, 3. Inorder (left, root, right) reads 1, 2, 3. Postorder (left, right, root) reads 1, 3, 2. All three visit the same three nodes; only the position of the root relative to the two subtrees changes โ€” first, middle, or last. Writing the recursive calls in the wrong order (say, right before left) silently produces a valid-looking but wrong sequence, and it's easy to not notice on a tree bigger than three nodes.

Use It
Binary Tree Inorder Traversal
inorder() from Build It, verbatim: left, root, right.
Easy
Binary Tree Preorder Traversal
preorder() from Build It, verbatim: root, left, right.
Easy
Binary Tree Level Order Traversal
levelOrder() from Build It โ€” the only change is grouping result by level: track how many nodes are in the queue at the start of each while-loop pass.
Medium
Maximum Depth of Binary Tree
This is height() from Build It under a different name โ€” 1 + max(depth(left), depth(right)), base case NULL returns 0.
Easy
Symmetric Tree
Write a helper that compares two subtrees as mirror images: left.left against right.right, left.right against right.left.
Easy
Invert Binary Tree
At each node, swap node.left and node.right, then recurse into both โ€” a base case of NULL, same shape as every traversal above.
Easy
Balanced Binary Tree
height() from Build It, extended to also check |height(left) - height(right)| <= 1 at every node, not just at the root.
Easy
Diameter of Binary Tree
At every node, the longest path through it is height(left) + height(right) โ€” compute height bottom-up once, tracking the best sum seen anywhere.
Easy
Same Tree
Two NULLs are the same tree; one NULL and one real node are never the same; otherwise compare values and recurse on both left pairs and right pairs.
Easy
Path Sum
Recurse down subtracting node.value from the target; the base case is a NULL node (fail) or a leaf where the remaining target has hit exactly zero (success).
Easy