Trees · Binary Tree Fundamentals

Binary Tree Traversal

Recursing into left and right subtrees and combining their results.

  • 7 min read
  • 15 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: passing constraints down
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Binary tree traversal is recursion shaped like the tree. Handle the empty tree, recurse into the left and right subtrees, and combine. Almost every tree problem takes one of two shapes. In the bottom-up shape, the children report results and the parent combines them (height, size, balance). In the top-down shape, the parent passes rules down to its children (a BST's valid range, the depth so far). Picking the right shape is most of the work.

We'll go from simple to more technical:

  1. The pattern: null base case, recurse, combine results on the way up.
  2. A second flavor: pass constraints down instead (validating a BST).
  3. Where tree walks run in production (DOMs, file systems, permissions).
  4. The bugs: missing base cases, local-only checks, and recomputing subtrees.

The approach

A tree has no single "front to back" order the way an array does. But every subtree is a smaller tree, so recursion fits it naturally. First decide what an empty tree returns. Then ask both subtrees for their answers, and combine them with the node itself.

The height of a tree is the number of nodes on its longest path down from the root. An empty tree has height 0. Any other tree has height 1 plus the taller of its two subtrees.

6

3

8

1

9

12

6

3

8

1

9

12

The answers come back from the bottom up:

NodeLeft heightRight heightReturns
1001
3102
12001
9012
8023
6234

Java

int height(TreeNode node) {
    if (node == null) return 0;               // an empty tree has height 0
    int left = height(node.left);
    int right = height(node.right);
    return 1 + Math.max(left, right);          // combine: this node plus the taller side
}

Python

def height(node: TreeNode | None) -> int:
    if node is None:
        return 0                               # an empty tree has height 0
    left = height(node.left)
    right = height(node.right)
    return 1 + max(left, right)                # combine: this node plus the taller side

Size, sum and balance checks are the same shape with a different combine step.

A second flavor: passing constraints down

Some problems need the other direction. Each call receives rules from its ancestors and passes a stricter version to its children. Validating a binary search tree is the clearest case. In a BST, everything in a node's left subtree is smaller than the node, and everything in its right subtree is larger. Comparing a node with its parent isn't enough.

Take a root of 10 whose left child is 5, and give 5 a right child of 12. Each node looks fine next to its parent: 12 is bigger than 5. But 12 sits in 10's left subtree, so it must be smaller than 10. Carrying an allowed range down catches it:

NodeAllowed rangeInside it?
10any valueyes
5below 10yes
12between 5 and 10no: the tree isn't a valid BST

Java

boolean isValidBST(TreeNode node, long min, long max) {
    if (node == null) return true;
    if (node.val <= min || node.val >= max) return false;
    return isValidBST(node.left, min, node.val)      // left subtree: same lower bound, tighter upper bound
        && isValidBST(node.right, node.val, max);    // right subtree: tighter lower bound, same upper bound
}

Python

def is_valid_bst(node: TreeNode | None, lo: float = float("-inf"), hi: float = float("inf")) -> bool:
    if node is None:
        return True
    if not lo < node.val < hi:
        return False
    return (is_valid_bst(node.left, lo, node.val)       # left subtree: same lower bound, tighter upper bound
            and is_valid_bst(node.right, node.val, hi))  # right subtree: tighter lower bound, same upper bound

Each step down narrows the range. A node deep in the left subtree still has to respect the root's upper bound, set many calls earlier.

Where you'll see it

Every hierarchy is walked this way: an HTML or JSON document, a file system, a folder tree with permissions. Permission inheritance is the top-down shape. A folder's effective permissions combine the rules of every folder above it, not just its parent's.

Which tool for which job?

You needUse
A value computed from the children (height, size, sum)bottom-up: return it from the recursion
A rule set by the ancestors (BST range, depth limit)top-down: pass it as a parameter
Two facts about each subtree (balanced and height)return both from one recursion
A very deep or skewed treean explicit stack instead of recursion
Level by levelBFS with a queue
Sorted order of a BSTin-order traversal

Common mistakes

MistakeWhat happensDo this instead
No null base caseNullPointerException / AttributeErrorhandle null/None first
Checking a BST node only against its parentinvalid trees passcarry the (low, high) range down
Integer.MIN_VALUE/MAX_VALUE as range sentinelstrees holding those values failnullable bounds (or long)
Calling a height helper at every nodeO(n log n) balanced, O(n^2) skewedcompute everything in one pass
Recursing on a 100,000-node chainstack overflowiterate with a stack

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. A recursive tree function reads node.val without checking for null first. What happens?

Show answer

It crashes as soon as the recursion reaches a missing child: a NullPointerException in Java or an AttributeError in Python. Every tree function needs to decide what an empty tree returns before it touches a node's fields.

2. A BST check only tests node.left.val < node.val < node.right.val at each node. Why does it miss invalid trees?

Show answer

It only compares each node with its own children. A node can be fine next to its parent and still break a rule set higher up, like 12 under 5 under 10 above. The tree looks right at every node and is still not a BST.

3. Why pass a shrinking (min, max) range down instead of comparing with one value?

Show answer

A node's allowed range comes from every ancestor at once. The range carries all of those rules down together, getting narrower at each step, so each node is checked against all of its ancestors, not just the nearest one.

Key takeaways

  • Most tree problems are bottom-up (combine the children's answers) or top-down (pass rules down as parameters).
  • Height, size and sums are bottom-up. BST validation is top-down.
  • A node can satisfy its parent and still break an older ancestor's rule. Carry the full range down.
  • Decide what an empty tree returns before reading any node's fields.
  • Calling a helper recursion at every node turns O(n) into O(n log n), or O(n^2) on a skewed tree.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needUse
A value from the childrenbottom-up return
A rule from the ancestorstop-down parameter
Two facts per subtreereturn both in one pass
Deep or skewed treesan explicit stack
LevelsBFS

Snippets worth knowing by heart

Java

static boolean valid(TreeNode n, Integer lo, Integer hi) {   // null = no bound
    if (n == null) return true;
    if ((lo != null && n.val <= lo) || (hi != null && n.val >= hi)) return false;
    return valid(n.left, lo, n.val) && valid(n.right, n.val, hi);
}

Python

def height_or_unbalanced(node) -> int:
    if node is None:
        return 0
    left = height_or_unbalanced(node.left)
    right = height_or_unbalanced(node.right)
    if left < 0 or right < 0 or abs(left - right) > 1:
        return -1                       # unbalanced, reported upward once
    return 1 + max(left, right)

The traps

  • No null base case: the first empty child crashes. Handle null first.
  • Parent-only BST checks: a node can fit its parent but break an ancestor's range. Carry (low, high) down.
  • MIN/MAX sentinels: a tree holding Integer.MIN_VALUE fails the check. Use nullable bounds, or long.
  • Height helpers at every node: that's O(n^2) on a skewed tree. Compute everything in one pass.
  • Deep recursion: a 100,000-node chain overflows the stack. Iterate with an explicit stack.

Staff-level points, one line each

  • 10,000 random inserts: height 35; sorted inserts: height 10,000.
  • Balance check on 1,023 nodes: 18,434 helper calls vs 2,047 in one pass.
  • Diameter with a height helper on a 500-node chain: 250,500 calls.
  • MIN/MAX int sentinels rejected a valid tree; nullable bounds accepted it.
  • Trees need no memoization; shared subtrees (DAGs) do.

Say it in an interview

  • "Children return results and the parent combines them - one pass, O(n)."
  • "For BST validation I pass the allowed range down from the ancestors."
  • "Space is O(height): log n if balanced, n if skewed."
  • "If the tree could be very deep, I'd switch to an explicit stack."

Practice

This skill comes with 15 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Binary Tree Traversal free

Validate a Binary Search Tree

MediumProCoding problemJava · Python

Check whether a binary tree is a binary search tree (BST): every value in a node's left subtree is smaller than the node, every value in its right subtree is larger (equal values aren't allowed), and that holds at every node, not just between parents and their children.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Binary Tree Traversal free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime