21

Tree Traversals

Visit every node exactly once — and pick the order that already answers the question.

intermediate~75 min6 simulations9 problems

▶ guided lesson⚡ 1-minute summary↻ 14 recall cards

before this: Recursion · Stacks · Queues

01 · why it exists

Why this exists, and the weight it carries

since1857

Binary tree

Arthur Cayley (the name "tree"); no single inventor of the linked binary tree in programs

In 1857 the mathematician Arthur Cayley published "On the theory of the analytical forms called trees" in the Philosophical Magazine. He was counting rooted, branching shapes while studying how calculus operators combine — and the name tree stuck. In programs, a node holding two links (left, right) became a standard building block of early list-processing and compilers; no one person is credited with it. Today every node in a Java TreeMap is built on it: a key and a value, left and right child references (plus a parent link and a red/black colour bit).

the problem it solvedRepresenting hierarchy and branching — anything where one thing splits into at most two smaller things of the same kind.

its jobs today

  • Java collections: TreeMap and TreeSet are red-black trees — binary search trees that rebalance themselves.
  • Compilers: javac turns a + b * c into a syntax tree whose operator nodes have two children.
  • Compression: Huffman coding builds a binary tree where left = 0 and right = 1; the path to a leaf is that character's code.

the weight it carries

Illustrative: 10 lakh (10⁶) keys. Kept in a balanced binary tree, the root-to-leaf path is about ⌈log₂ 10⁶⌉ = 20 nodes, so any lookup touches ~20 nodes instead of scanning up to 10⁶. The same keys in a skewed tree make that path 10⁶ long.

without it

Hierarchies would be flattened into lists: every "find", "insert in order" or "evaluate this expression" becomes a linear scan.

when · why · how

when
Data that branches (expressions, decisions, sorted keys, folder-like structures) or must stay ordered while it changes.
why
A balanced tree keeps every path O(log n) long; the price is pointer-chasing (two references per node) and the risk of an O(n) skewed shape if nothing rebalances it.
how
class TreeNode { int val; TreeNode left, right; } on LeetCode; TreeMap<K, V> when you need a balanced ordered tree in real code.

02 · plain english

The idea in 30 seconds

A binary tree is a node with a value and at most two children — and each child is itself a binary tree.

A traversal visits every node once. Depth-first (pre, in, post order) goes deep using a stack; breadth-first (level order) goes level by level using a queue.

Many tree problems are just the right visiting order plus one line of work at each visit.

picture this

Your File Explorer folder tree, walked two ways. Depth-first: open Projects and everything inside it before you touch Downloads — that's how the Windows tree command prints folders (preorder). Breadth-first: list every top-level folder, then every folder one level down — so the first match you find is the shallowest one.

03 · remember

What you must know

  • Terminology: root (top, no parent), leaf (no children), edge (parent→child link). Depth = edges from the root down to a node. Height = edges on the longest path from a node down to a leaf. A tree's height is its root's height.
  • Shapes: full = every node has 0 or 2 children. Complete = every level full except maybe the last, filled left to right (heaps). Perfect = all levels full (2^(h+1) − 1 nodes). Balanced = at every node the two subtree heights differ by at most 1, so h = O(log n).
  • *DFS orders differ only in when you touch the node:* preorder = before the children (copy, serialize), inorder = between them (sorted order in a BST), postorder = after them (heights, sizes, deleting).
  • Iterative DFS: an ArrayDeque does the call stack's job. Inorder loop: dive left pushing every node, pop one, visit it, go right. Postorder trick: a root → right → left preorder, reversed.
  • BFS uses a queue. Snapshot size = queue.size() at the start of each round to process exactly one level — the trick behind every "level" problem.
  • Space: DFS holds one root-to-node path → O(h). BFS holds about one level → O(w), the widest level. A perfect tree is short and wide (DFS wins); a skewed chain is tall and thin (BFS wins).
  • *When the order is the answer:* level lists, zigzag, right side view, averages, next pointers and serialization are all "traverse in the right order, do one line of work per node".
  • A tree is a graph that only points down. Record each node's parent in a HashMap and you can walk up too — then a BFS from any node measures distances (All Nodes Distance K).

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Preorder · inorder · postorder walk

Type pre, in or post. The call stack (right) is the path from the root to the current node; the output fills in when the node is *visited*.

loading simulation…
sim

Inorder without recursion

An explicit stack does exactly what the call stack did: remember the nodes we still have to come back to.

loading simulation…
sim

Level-order BFS with a queue

Amber nodes are waiting in the queue. `size` freezes the current level so the next level never mixes in.

loading simulation…
sim

Right side view — the last node of each level

Same BFS, one extra line: the node polled at i = size − 1 is the one you see from the right, even when it lives in the left subtree.

loading simulation…
sim

Serialize and deserialize with null markers

Preorder writes every node and every null (#). The reader consumes the tokens in the same order, so each subtree rebuilds itself.

loading simulation…
sim

All nodes distance K — walk up, not just down

Step 1 records every parent. Step 2 is a BFS from the target over left, right and parent links; after k rounds the queue is the answer.

loading simulation…

05 · cost

Time & space, explained

Traversals (n nodes, height h, widest level w)
TimeExtra spacewhy
DFS (pre/in/post), recursiveO(n)O(h)Each node is entered once; the call stack only holds the current root-to-node path.
DFS with an explicit ArrayDeque stackO(n)O(h)Each node is pushed and popped once; same path on the stack — but on the heap, so deep trees don't overflow.
Morris inorder (threads)O(n)O(1)Temporarily points empty right links back to ancestors, then removes them; each edge is walked a constant number of times.
BFS level order with a queueO(n)O(w)Each node is offered and polled once; the queue holds about one level (up to ~n/2 in a perfect tree).
Serialize / deserialize (preorder + markers)O(n)O(n)n values and n + 1 null markers are written once and read once; the string itself is O(n).
Parent map + BFS from a nodeO(n)O(n)One DFS fills the map; the BFS offers each node at most once thanks to the seen set.
Stack vs queue memory at the extremes (n ≈ 10⁶)
DFS stack (h)BFS queue (w)why
Perfect tree, n = 2²⁰ − 1 = 1,048,57520 frames524,288 nodes20 levels deep, but the last level alone holds 2¹⁹ nodes — half the tree waits in the queue at once.
Skewed chain, n = 10⁶10⁶ frames → StackOverflowError1 nodeThe tree is a linked list in disguise: recursion depth equals n, while each level has one node.

06 · java

Raw vs Collections Framework

The TreeNode class

javaTreeNode (same shape LeetCode gives you)java.util.*
/** One node of a binary tree: a value plus links to at most two children. */class TreeNode {    int val;    TreeNode left;   // null when there is no left child    TreeNode right;  // null when there is no right child     TreeNode(int val) {        this.val = val;    }     TreeNode(int val, TreeNode left, TreeNode right) {        this.val = val;        this.left = left;        this.right = right;    }}
  • A tree is just nodes holding references. null means "no child here" — every leaf has two nulls.
  • On LeetCode, TreeNode is predefined; you never write it. In real code you would make the fields private or use a record for immutable trees.
  • n nodes always have exactly n − 1 edges (every node except the root has one parent) and n + 1 null child slots.

Depth-first: recursion vs an explicit stack

javarecursivejava.util.*
class RecursiveDfs {    /** Root, then left subtree, then right subtree. */    static void preorder(TreeNode node, List<Integer> out) {        if (node == null) return;        out.add(node.val);                 // visit BEFORE the children        preorder(node.left, out);        preorder(node.right, out);    }     /** Left subtree, then root, then right subtree. */    static void inorder(TreeNode node, List<Integer> out) {        if (node == null) return;        inorder(node.left, out);        out.add(node.val);                 // visit BETWEEN the children        inorder(node.right, out);    }     /** Left subtree, then right subtree, then root. */    static void postorder(TreeNode node, List<Integer> out) {        if (node == null) return;        postorder(node.left, out);        postorder(node.right, out);        out.add(node.val);                 // visit AFTER the children    }}
  • The three recursive versions are identical except for where out.add sits. Learn one, move one line, get the others.
  • Iterative preorder pushes the right child first so the left one is on top and comes out first.
  • Iterative inorder: dive left pushing everything, pop one, visit it, then go right. Bas itna yaad rakho — BST problems reuse this exact loop (kth smallest, BST iterator).
  • Iterative postorder trick: a root → right → left preorder, reversed, is left → right → root.
  • Use ArrayDeque as the stack, never java.util.Stack: Stack extends the synchronized Vector and is slower and considered legacy.

Breadth-first: the queue

javaBFS with ArrayDequejava.util.*
class Bfs {    /** Visits nodes level by level, left to right. */    static List<Integer> levelOrder(TreeNode root) {        List<Integer> out = new ArrayList<>();        if (root == null) return out;          // ArrayDeque rejects null        Deque<TreeNode> queue = new ArrayDeque<>();        queue.offer(root);        while (!queue.isEmpty()) {            TreeNode node = queue.poll();      // oldest discovered node first            out.add(node.val);            if (node.left != null) queue.offer(node.left);            if (node.right != null) queue.offer(node.right);        }        return out;    }}
  • offer adds at the rear, poll removes from the front — first discovered, first processed. That is why BFS goes level by level.
  • ArrayDeque throws NullPointerException on null elements. Check children before offering them.
  • Need levels separately? Read queue.size() into a variable before the inner loop (see Level Order Traversal).

When nulls are data: LinkedList as the queue

javaBFS codec with LinkedListjava.util.*
class Codec {    /** Level order, "#" for a missing child: "1,2,3,#,#,4,5,#,#,#,#". */    public String serialize(TreeNode root) {        if (root == null) return "";        StringBuilder sb = new StringBuilder();        Queue<TreeNode> queue = new LinkedList<>();   // LinkedList allows null; ArrayDeque does not        queue.offer(root);        while (!queue.isEmpty()) {            TreeNode node = queue.poll();            if (node == null) { sb.append("#,"); continue; }            sb.append(node.val).append(',');            queue.offer(node.left);            queue.offer(node.right);        }        return sb.toString();    }     public TreeNode deserialize(String data) {        if (data.isEmpty()) return null;        String[] tokens = data.split(",");        TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));        Deque<TreeNode> parents = new ArrayDeque<>();        parents.offer(root);        int i = 1;        while (!parents.isEmpty()) {          // tokens come in pairs: left child, right child            TreeNode parent = parents.poll();            if (!tokens[i].equals("#")) {                parent.left = new TreeNode(Integer.parseInt(tokens[i]));                parents.offer(parent.left);            }            i++;            if (!tokens[i].equals("#")) {                parent.right = new TreeNode(Integer.parseInt(tokens[i]));                parents.offer(parent.right);            }            i++;        }        return root;    }}
  • Serialization must record the missing children, so the queue has to hold null. LinkedList allows it; ArrayDeque does not.
  • Build strings with StringBuilder.append — += in a loop copies the whole string every time (O(n²)).
  • data.split(",") drops trailing empty strings, so a trailing comma is harmless.
Java API you will actually use
calldoescost
Deque<TreeNode> st = new ArrayDeque<>()Stack for iterative DFS (push / pop / peek work on the head)O(1) amortized
queue.offer(x) / queue.poll()Enqueue at the rear / dequeue from the front (BFS)O(1) amortized / O(1)
queue.size() / isEmpty()Snapshot one level's width; loop conditionO(1)
new LinkedList<>() (as a Queue)A queue that accepts null — needed only when nulls are dataO(1) per offer/poll
level.addFirst(x) / addLast(x) on a DequeBuild a zigzag level from either endO(1)
Collections.reverse(list)Turns root-right-left into postorder; reverses a levelO(n)
Map<TreeNode, TreeNode> parent = new HashMap<>()Parent links, so you can walk up the treeO(1) average
Set<TreeNode> seen; seen.add(x)add returns false if x was already there — one call checks and marksO(1) average
sb.append(x) / s.split(",")Write / read a serialized treeO(1) amortized / O(length)

Copy, print or serialize a tree top-down

→ Preorder DFS

The root comes first, so a reader can rebuild it in the same order.

Visit a BST in sorted order

→ Inorder DFS

Left (smaller) → node → right (larger).

A node's answer needs its children's answers

→ Postorder (see Tree Recursion)

Children finish before the parent combines their results.

Anything per level: lists, averages, right view, next pointers

→ BFS with a size snapshot

The queue naturally holds one level at a time.

Tree may be very deep (10⁵ skewed nodes)

→ Iterative DFS with ArrayDeque, or BFS

Avoids StackOverflowError: heap memory is far larger than the thread stack.

Queue that must hold null markers

→ LinkedList as Queue

ArrayDeque rejects null.

Distances that can go up through parents

→ HashMap parent links + BFS with a seen set

Turns the tree into an undirected graph; BFS visits nodes in order of distance.

07 · recognise

Spot the pattern

"Level", "row", "zigzag", "right side view", "average of each level"

BFS with size = queue.size() per round

"Can you do it without recursion?"

Explicit ArrayDeque stack — inorder: dive left, pop, visit, go right

Values of a BST in sorted order, k-th smallest

Inorder traversal

"Copy", "serialize", "encode" a tree

Preorder (or BFS) with a marker for every null

"Width", positions, gaps that count

Number nodes like a heap array (2p, 2p + 1); re-base per level

"Distance k from a node", "time to spread through the tree"

Parent map → undirected graph → BFS from the node

"Point each node to its right neighbour"

Level order; for O(1) space walk the level you already linked

"Inorder with O(1) extra space"

Morris traversal (temporary threads, then restore)

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Offering null children to an ArrayDeque — it throws. Check if (node.left != null) first.
  • Using queue.size() directly in the for condition: it changes while you offer children. Store it in a variable first.
  • Iterative preorder pushing the left child before the right — you get root → right → left.
  • Iterative inorder looping only while (!stack.isEmpty()) — it stops before the last right subtree. The condition is cur != null || !stack.isEmpty().
  • Right side view by following only .right pointers — misses levels where the rightmost node lives in the left subtree.
  • Serializing without separators (1, 23 vs 12, 3) or without null markers (different shapes, same string).
  • Zigzag by swapping the order you offer children — the next level comes out scrambled. Only change where values go in the output list.
  • Max width without re-basing positions: they double every level and overflow after ~63 levels even in a long.
  • BFS on a tree-with-parents without a seen set: you walk back the way you came and count nodes twice.

tricks

  • Before coding, say the order out loud: "I need levels, so BFS" or "I need sorted order, so inorder". Half the problem is choosing it.
  • Chalo, dry-run karte hain: draw a 5–7 node tree with one gap and one deep branch, and walk your loop by hand. That covers almost every bug.
  • Edge cases to test every time: empty tree, single node, left-skewed, right-skewed, a level with gaps, negative values.
  • State space for both extremes: DFS O(h) — O(log n) balanced, O(n) skewed; BFS O(w) — up to n/2 for a perfect tree.
  • When asked "can you do it without recursion?", reach for ArrayDeque: a stack for DFS, a queue for BFS.
  • Write the size-snapshot BFS loop from memory until it takes you under a minute — it solves five problems in this chapter.

09 · why & how

Questions you might be too shy to ask

Does "binary tree" mean the values are sorted?

No. "Binary" only describes the shape: each node has at most two children. The values can be in any order, so inorder of a plain binary tree is not sorted. Only a binary search tree (BST) promises left < node < right. Read the problem title carefully — it changes the whole solution.

How do I read LeetCode's tree input like [1,null,2,3]?

It is level order, left to right, with null for a missing child. 1 is the root. Its children are null (left) and 2 (right). The next tokens are 2's children, so 3 is 2's left child. A null gets no child slots of its own, and trailing nulls are dropped.

Height vs depth — which is which?

Depth looks up: how many edges from the root down to this node (root = 0). Height looks down: how many edges from this node to its deepest leaf (leaf = 0). The tree's height = the root's height = the deepest node's depth. LeetCode's "maximum depth" counts nodes, so one node has depth 1 there.

How do I choose between preorder, inorder, postorder and level order?

Ask what you need at each node. Need levels or "nearest"? Level order. Need sorted order from a BST? Inorder. Need to write the tree down so it can be rebuilt? Preorder (root first). Need the children's answers before the node's? Postorder — that's the next chapter, Tree Recursion.

How does the recursion know to come back up to the parent?

Every call gets its own frame on the call stack, holding its own node. When walk(node.left) hits null, it returns — and Java simply continues the parent's frame at the next line. So after the left subtree finishes, the parent runs out.add(node.val) and then walk(node.right). Nothing magic: "come back" just means "the inner call finished". Watch the call-stack panel in the sim.

Why is the base case node == null and not "node is a leaf"?

Checking null covers every case with one line: a leaf calls on two nulls and stops. A leaf-only check breaks on a node with one child — the call on its missing side gets null and crashes with NullPointerException. It also handles the empty tree: root = null returns an empty list straight away. For BFS, add if (root == null) return result; before offer(root), because ArrayDeque rejects null.

Why is DFS space O(h) but BFS space O(w)?

DFS only remembers the path from the root to where it is now — at most h + 1 nodes. BFS remembers the whole frontier: the rest of the current level plus the next one, so its memory follows the widest level w. A perfect tree of about a million nodes (2²⁰ − 1) has h = 19 but w = 524,288 on its last level.

Graphs need a visited set. Why don't tree traversals?

In a tree, links only point down and every node has exactly one parent. So there is only one way to reach each node — you can never arrive twice. The moment you add parent links (All Nodes Distance K), you can walk back up the way you came. Then you need seen, exactly like a graph.

Level problems — do I have to use BFS?

BFS is the natural fit, but DFS works too: pass depth down as a parameter. When depth == result.size(), you are the first node of a new level, so open a new list. Right side view with DFS: visit the right child first and record the first node you see at each depth. Pick BFS when you need to stop at the first (nearest) level; either is fine for collecting all levels.

Why did all my levels come out identical (or empty)?

You probably reused one list: result.add(level); level.clear();. result stores a reference to that same list, so clearing it clears every "saved" level. Create a fresh List<Integer> level = new ArrayList<>() at the start of each round. Simple hai — one new list per level.

Why does inorder traversal matter so much?

On a binary search tree, inorder visits values in sorted order. Validate BST, kth smallest and BST iterator are "just inorder" — usually the iterative loop from this chapter, paused and resumed.

Will recursion overflow the stack in an interview?

Usually not on LeetCode — most test trees are shallow. (People also report that its Java judge has a bigger stack than a default thread, but that is community-reported, not documented.) Still, constraints allow 10⁴–10⁵ nodes, and a fully skewed tree that size can overflow a default Java thread stack — how deep is too deep depends on your setup. Mention the risk and that an iterative ArrayDeque version (or BFS) fixes it — interviewers want to hear that.

Can I rebuild a tree from just one traversal?

Not from plain values — preorder [1, 2] could be 2 as a left or a right child. You need either two traversals (preorder + inorder, see Tree Recursion) or one traversal that also records the nulls, which is exactly what serialization does.

Do interviewers expect Morris traversal?

Rarely as the first answer. It's the follow-up to "inorder in O(1) extra space". Know the idea — thread the rightmost node of the left subtree back to the current node, then remove the thread — and its catch: it modifies the tree while running, so no other code may read the tree during the walk.

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #94
    Binary Tree Inorder Traversalmust-do
    EasyMicrosoftBloombergGoogleAmazonMeta
  2. #102
    Binary Tree Level Order Traversalmust-do
    MediumBloombergAmazonMicrosoftAppleOracle+1
  3. #637
  4. #199
    Binary Tree Right Side Viewmust-do
    MediumMetaOracleAmazonByteDance / TikTokUber+1
  5. #103
    Binary Tree Zigzag Level Order Traversal
    MediumAmazonMicrosoftBloombergLinkedInGoogle+1
  6. #117
    Populating Next Right Pointers in Each Node II
    MediumBloombergMetaMicrosoftAmazonGoogle
  7. #662
    Maximum Width of Binary Tree
    MediumAmazonBloombergGoogleMicrosoftMeta
  8. #863
    All Nodes Distance K in Binary Treemust-do
    MediumMetaAmazonMicrosoftGoogleBloomberg
  9. #297
    Serialize and Deserialize Binary Treemust-do
    HardLinkedInMicrosoftByteDance / TikTokUberAmazon+1

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #230
    Kth Smallest Element in a BST
    Mediumin BST

    Inorder on a BST is sorted order — stop the iterative inorder loop at the k-th pop.

  2. #173
    Binary Search Tree Iterator
    Mediumin BST

    The iterative inorder loop, paused between calls: the stack keeps the left spine.

11 · revise

The whole chapter on one screen

9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    A traversal visits every node once. DFS (pre / in / post) goes deep with a stack; BFS (level order) goes level by level with a queue.

  2. use it when

    Levels, rows, "nearest" → BFS. Sorted BST order → inorder. Copy / serialize → preorder. Children before parent → postorder.

  3. template

    BFS: offer(root); while (!q.isEmpty()) { int size = q.size(); for (i < size) { poll; offer kids; } }

  4. template

    Iterative inorder: while (cur != null || !st.isEmpty()) { while (cur != null) { push; cur = cur.left; } cur = pop(); visit; cur = cur.right; }

  5. cost

    All traversals O(n) time. DFS space O(h): 20 frames for a balanced 10⁶-node tree, 10⁶ for a chain. BFS space O(w): up to n/2.

  6. trap

    for (int i = 0; i < queue.size(); i++) mixes levels — the queue grows inside the loop. Freeze size first.

  7. trap

    ArrayDeque rejects null: check children before offer. Need nulls in the queue (serialization)? Use LinkedList.

  8. pro tip

    Distance can go up too? Store parents in a HashMap, then BFS with a seen set — the tree is now an undirected graph.

  9. pro tip

    Serialize = preorder + # for every null + a separator. One traversal with markers rebuilds the exact tree.

12 · recall

Flip, remember, repeat

card 1 / 14
recall

Preorder, inorder, postorder: where does the "visit" line go in each?

tap or Space to flip ↻

Question 1: Preorder, inorder, postorder: where does the "visit" line go in each?

13 · check yourself

Quick quiz

  1. Preorder of the tree root 1, left child 2 (with children 4 and 5), right child 3?

  2. What is the extra space of recursive DFS on a tree with n nodes and height h?

  3. In BFS level order, why store `int size = queue.size()` before the inner loop?

  4. Why does `queue.offer(node.left)` sometimes throw with ArrayDeque?

  5. A perfect tree has 2²⁰ − 1 nodes. Roughly how many nodes can a level-order queue hold at once?

  6. Zigzag level order: what should change on odd levels?

  7. Maximum Width numbers nodes like a heap. A node at position 5 has children at…

  8. All Nodes Distance K: why does the BFS need a `seen` set even though a tree has no cycles?