Skip to main content

Binary Tree Traversals in Java: Inorder, Preorder, Postorder and Level Order (Recursive and Iterative)

Binary tree traversal in Java on one numbered nine-node tree: recursive and iterative inorder, preorder and postorder with stack dry-run tables, one-stack postorder, Morris traversal, level order and zigzag with a queue, height, an O(n) versus O(n squared) diameter comparison by counted visits, a real StackOverflowError on a one-million-node skewed tree, and exam questions with answers.

Binary tree traversal in Java is the question that hides three separate questions. The first is which of inorder, preorder and postorder writes the root first, in the middle or last — a coin-flip for anyone who memorised the names instead of the idea. The second is how to do any of them without recursion, which is where most interview answers to postorder fall apart. The third is what happens when the tree is a million levels deep, which recursion answers with a StackOverflowError and an explicit stack answers with a result. This page takes all three in that order, on one nine-node tree that is numbered in every diagram, so you can check each picture against the program output. The reading flow is for a beginner: what a traversal is, the three recursive versions, the depth limit that breaks them, the iterative versions with a stack table for each, level order, height and diameter, rebuilding a tree from two traversals, and exam-style questions with answers. Extra versions (one-stack postorder, Morris) sit in collapsed sections.
Versions this was built and run against. JDK 25.0.5+8 (Temurin, LTS), JUnit 6.1.3 (latest GA on Maven Central, and the version the sibling modules use), Maven 3.9.11. No other library: only java.util. Everything quoted below comes from a committed file in the binary-tree-traversals module of the java-interview-lab repository, and the 15 tests there pass. The stack-overflow depths are measurements from one shared 2-core sandbox; the page says where that matters.

A traversal visits every node once, in a chosen order

A list has an obvious order: first to last. A tree does not. From a node you can go to the left child, the right child, or back to the parent, so “visit every node” has many valid orders. A traversal is a rule that picks one of them and guarantees each node is visited exactly once. The words you need, shown on the tree that every diagram below reuses: the root is the top node; a leaf has no children; a subtree is any node together with everything below it; a level is a row of nodes at the same distance from the root; the height is how many levels there are.
The sample tree used in every diagram on this page 4 2 7 5 1 3 8 6 9level 0level 1level 2level 3rootleafleafleafleafsubtree rooted at 3Green = leaf (no children). Dashed box = the subtree rooted at 3. Every node except the root has exactly one parent.
The tree has 9 nodes, 4 leaves (4, 7, 8, 9) and 4 levels. Node 3 has no left child, which is deliberate: real trees are uneven, and an uneven tree is what exposes a wrong traversal. In code a node is just a value and two links (TreeNode.java):
public final class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(int val) {
        this.val = val;
    }

    public TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}
The sample tree is built once in Trees.java and printed in output/01-sample-tree-all-traversals.txt as 1(2(4,5(7,-)),3(-,6(8,9))), which reads value(left,right) with - for a missing child.
Going deeper: other words exam papers use
  • Full binary tree: every node has 0 or 2 children. Complete: every level is full except possibly the last, which fills left to right. Perfect: all leaves on the same level. The sample tree is none of these, on purpose.
  • Related posts on this blog: Implementing Binary Search Tree in Java and Implementation of Stack in Java, the two data structures the rest of this page leans on.

The one idea behind the three depth-first orders

Walk around the outside of the tree, keeping the tree on your left hand, starting above the root. You will pass every node three times: once on the way down into it, once when you come back up from its left subtree, and once when you come back up from its right subtree. Preorder, inorder and postorder are the same walk; they only differ in which of the three passes they write down.
Every node is passed three times. The three orders differ only in which pass they write down.left subtreeright subtreeN1st pass: arrive at NPREorder writes N here (before the children)2nd pass: back from leftINorder writes N here (between the children)3rd pass: back from rightPOSTorder writes N here (after the children)The prefix (pre / in / post) tells you where the root goes relative to its two subtrees.
The diagram shows one node N with its two subtrees. Write N on the first pass and you get preorder (N, left, right). Write it on the second pass and you get inorder (left, N, right). Write it on the third and you get postorder (left, right, N). The left subtree is always finished before the right one; only the position of the root moves.
The name is the position of the root. Pre = root before the subtrees. In = root between them. Post = root after them. If you can reproduce the diagram above on a blank page you can derive any of the three on the spot, and you do not need to memorise “NLR, LNR, LRN” as three separate facts.

Recursive traversals: three methods, one line moved

Recursion matches the definition directly: to traverse a tree, handle the node and traverse its two subtrees. An empty subtree (null) is the base case and does nothing. The three versions below are the real methods from RecursiveTraversals.java; compare them line by line and notice that only the position of out.add(node.val) changes.
private static void preorder(TreeNode node, List<Integer> out) {
    if (node == null) {
        return;
    }
    out.add(node.val);
    preorder(node.left, out);
    preorder(node.right, out);
}
private static void inorder(TreeNode node, List<Integer> out) {
    if (node == null) {
        return;
    }
    inorder(node.left, out);
    out.add(node.val);
    inorder(node.right, out);
}
private static void postorder(TreeNode node, List<Integer> out) {
    if (node == null) {
        return;
    }
    postorder(node.left, out);
    postorder(node.right, out);
    out.add(node.val);
}
Run on the sample tree, the three methods print these lists (the first three lines of output/01-sample-tree-all-traversals.txt):
recursive inorder        : [4, 2, 7, 5, 1, 3, 8, 6, 9]
recursive preorder       : [1, 2, 4, 5, 7, 3, 6, 8, 9]
recursive postorder      : [4, 7, 5, 2, 8, 9, 6, 3, 1]

Now the same lists as numbers on the tree. In each picture the amber badge is the position at which that node is written, so the badge sequence 1, 2, 3, … is the output read left to right.

Preorder (N, left, right) 4 3 2 2 7 5 5 4 1 1 3 6 8 8 6 7 9 9Order: 1 → 2 → 4 → 5 → 7 → 3 → 6 → 8 → 9Preorder = root, left subtree, right subtree. The root is first and its whole left subtree (2, 4, 5, 7) comes before anything on the right.Compare with the list printed above: the badges match it position for position.
Inorder (left, N, right) 4 1 2 2 7 3 5 4 1 5 3 6 8 7 6 8 9 9Order: 4 → 2 → 7 → 5 → 1 → 3 → 8 → 6 → 9Inorder = left subtree, root, right subtree. The first node written is the leftmost one (4), the last is the rightmost (9).Notice the root 1 lands in the middle, at position 5, with the whole left subtree before it.
Postorder (left, right, N) 4 1 2 4 7 2 5 3 1 9 3 8 8 5 6 7 9 6Order: 4 → 7 → 5 → 2 → 8 → 9 → 6 → 3 → 1Postorder = left subtree, right subtree, root. Children always come before their parent, and the root is always last.That property is why postorder is used to delete a tree or to evaluate an expression tree.
Inorder deserves one extra fact because exams love it: the inorder traversal of a binary search tree is sorted. Left subtree holds smaller keys, right subtree larger ones, and inorder writes left, root, right. A BST built by inserting 50, 30, 70, 20, 40, 60, 80 gives (output/11-bst-inorder-sorted.txt):
inserted in this order : [50, 30, 70, 20, 40, 60, 80]
tree                   : 50(30(20,40),70(60,80))
inorder                : [20, 30, 40, 50, 60, 70, 80]
This only holds for a search tree. The sample tree is a plain binary tree, so its inorder list 4 2 7 5 1 3 8 6 9 is not sorted. “Inorder is sorted” is a property of the BST ordering rule, not of inorder traversal itself, and exam questions are written to catch exactly that confusion.

Going deeper

Where recursion breaks: a tree one million levels deep

Every recursive call takes a frame on the thread’s call stack, and that stack has a fixed size. For a balanced tree this is irrelevant: a million nodes are about 20 levels deep, so recursion goes 20 calls deep. The problem is a skewed tree, where each node has one child. It is what you get by inserting already-sorted keys into a plain binary search tree, and then the tree is a linked list and the recursion is as deep as the list is long.
Each recursive call is a frame on the thread stack. The stack has a fixed size.probe(node 1)probe(node 2)probe(node 3)… many more frames …probe(node 29514)bottom of the stack (oldest call)top: java.lang.StackOverflowErrorLeft-skewed tree: every node has only a left child,so the call chain is as long as the tree is tall.On this machine, default 1024 KB stack:the probe got to depth 29514 (3rd attempt).Frames are created per call, not per node youstill need to visit, so a 1,000,000-node chaincannot fit. A balanced tree this size is ~20 deep.
The picture shows the left-skewed chain of 1,000,000 nodes that DeepRecursionDemo.java builds with a loop (building it recursively would overflow first). The call chain grows one frame per node until the JVM runs out of stack. Here is the probe method that counts how deep it got; it has the same shape as the recursive inorder, plus one counter:
private static void probe(TreeNode node, int depth) {
    if (node == null) {
        return;
    }
    if (depth > deepestEntered) {
        deepestEntered = depth;
    }
    probe(node.left, depth + 1);
    probe(node.right, depth + 1);
}
Run on the real RecursiveTraversals.inorder and then on the iterative one, with the default stack (output/12-deep-recursion-default-stack.txt):
Depth reached by a recursive probe before StackOverflowError (3 attempts in one JVM):
  attempt 1: StackOverflowError after reaching depth 21258
  attempt 2: StackOverflowError after reaching depth 29514
  attempt 3: StackOverflowError after reaching depth 29514

RecursiveTraversals.inorder on the same tree:
  caught java.lang.StackOverflowError
  stack trace length: 1024 frames (the JVM truncates traces; the real depth is larger)
      at com.ankurm.interviewlab.bintree.RecursiveTraversals.inorder(RecursiveTraversals.java:20)
      at com.ankurm.interviewlab.bintree.RecursiveTraversals.inorder(RecursiveTraversals.java:20)
      at com.ankurm.interviewlab.bintree.RecursiveTraversals.inorder(RecursiveTraversals.java:20)
      at com.ankurm.interviewlab.bintree.RecursiveTraversals.inorder(RecursiveTraversals.java:20)
      ...

IterativeTraversals.inorder on the same tree:
  visited 1000000 nodes, first=1000000, last=1, no error
Three things in that output are worth reading slowly. The error is java.lang.StackOverflowError, an Error and not an Exception, so a catch (Exception e) will not catch it. The stack trace has exactly 1024 frames because the JVM truncates traces by default; the real depth is much larger and the trace cannot tell you by how much. And the iterative version, on the very same tree, visited all 1,000,000 nodes without complaint, because its stack lives on the heap. The depth at which it fails depends on the stack size, and it is not a fixed number. The same program was run with three thread stack sizes (output/13-deep-recursion-xss512k.txt and output/14-deep-recursion-xss4m.txt are the other two runs):
Stack size (-Xss)Probe depth, attempt 1Probe depth, attempt 3
512 KB5,17513,130
1024 KB (default here)21,25829,514
4096 KB51,051127,818
Do not quote these depths as “the limit”. They are machine, JVM-build and run dependent, and they even differ between attempts inside one JVM: attempt 1 managed 21,258 on the default stack and attempt 3 managed 29,514, because the JIT compiler replaces the large interpreter frames with smaller compiled ones as the method gets hot. The honest statement is the shape: depth scales roughly with stack size, it is in the tens of thousands for a default stack, and a tree whose height is controlled by user data can exceed it.
That is the case for the iterative versions that fill the next sections. The unit test DeepTreeTest.java pins the same behaviour without depending on your machine’s default: it runs the traversals on a thread with an explicit 256 KB stack and asserts that recursion throws and iteration finishes on a 200,000-node chain.

Going deeper

  • StackOverflowError, Java SE 25 API.
  • Raising -Xss only moves the cliff. It also multiplies memory per thread, so it is a poor fix for a service that runs thousands of threads.
  • Self-balancing trees (AVL, red-black) keep the height near log n, which is why TreeMap never has this problem; see Mastering TreeSet in Java.

Iterative inorder: let the stack remember the way back

Recursion works because the call stack silently remembers “where to resume”. An iterative traversal does the same with an explicit Deque used as a stack. For inorder the rule is three sentences: go left as far as you can, pushing every node you pass; when you cannot go left, pop a node, write it down, and then move to its right child; stop when there is nothing to go to and the stack is empty. The method, from IterativeTraversals.java. (The lines that feed the step tables are left out of this listing; they do not change the logic.)
public static List<Integer> inorder(TreeNode root, StepListener listener) {
    List<Integer> out = new ArrayList<>();
    Deque<TreeNode> stack = new ArrayDeque<>();
    TreeNode current = root;
    while (current != null || !stack.isEmpty()) {
        while (current != null) {          // go as far left as possible, remembering the way back
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();             // nothing further left: this node is next
        out.add(current.val);
        current = current.right;           // then the whole right subtree
    }
    return out;
}
Here is the full dry run on the sample tree, produced by the real method through a listener hook (output/02-inorder-iterative-steps.txt). The stack column lists the stack from bottom to top.
stepactionstack (bottom..top)output so far
1push 1[1][]
2push 2[1 2][]
3push 4[1 2 4][]
4pop 4, visit[1 2][4]
5pop 2, visit[1][4 2]
6push 5[1 5][4 2]
7push 7[1 5 7][4 2]
8pop 7, visit[1 5][4 2 7]
9pop 5, visit[1][4 2 7 5]
10pop 1, visit[][4 2 7 5 1]
11push 3[3][4 2 7 5 1]
12pop 3, visit[][4 2 7 5 1 3]
13push 6[6][4 2 7 5 1 3]
14push 8[6 8][4 2 7 5 1 3]
15pop 8, visit[6][4 2 7 5 1 3 8]
16pop 6, visit[][4 2 7 5 1 3 8 6]
17push 9[9][4 2 7 5 1 3 8 6]
18pop 9, visit[][4 2 7 5 1 3 8 6 9]
Read it as a story. Steps 1–3 walk down the left spine, pushing 1, 2, 4. Step 4 pops 4, the leftmost node, and writes it: the first output. Step 5 pops 2; 2 has a right child, so steps 6–7 push 5 and then walk left from 5 to push 7. And so on until step 18 pops 9 and the stack is empty. There are 9 pushes and 9 pops, 18 steps in all: every node enters the stack once and leaves it once, which is why this is O(n).
The stack always holds the ancestors we still owe a visit toafter step 3push 4124output:[]after step 4pop 4, visit12output:[4]after step 7push 7157output:[4 2]after step 10pop 1, visit(empty)output:[4 2 7 5 1]after step 14push 868output:[4 2 7 5 1 3]Top of the stack is the highest box. Step numbers refer to the table above.
Five of those steps drawn as stacks. After step 3 the stack is the path 1 → 2 → 4 from the root to the leftmost node; after step 10 it is empty because 1 is the root and everything left of it has been written. The stack is never more than the height of the tree, here at most 3 entries.
The fingerprint of a wrong iterative inorder: the loop condition while (!stack.isEmpty()) alone. It exits immediately on the first iteration, because the stack starts empty and the root has not been pushed yet. The correct condition is current != null || !stack.isEmpty(): keep going while there is a node to descend into or a node waiting to be written.

Iterative preorder: push the right child first

Preorder is the easiest iterative one (IterativeTraversals.java): pop a node, write it, push its children. A stack gives back the most recently pushed item first, so to visit the left child before the right one you must push the right child first.
public static List<Integer> preorder(TreeNode root, StepListener listener) {
    List<Integer> out = new ArrayList<>();
    if (root == null) {
        return out;
    }
    Deque<TreeNode> stack = new ArrayDeque<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        out.add(node.val);
        if (node.right != null) {
            stack.push(node.right);        // pushed first so it is popped last
        }
        if (node.left != null) {
            stack.push(node.left);
        }
    }
    return out;
}
The dry run (output/03-preorder-iterative-steps.txt); the top of the stack is the right-hand end of each list:
stepactionstack (bottom..top)output so far
1pop 1, visit, push its children[3 2][1]
2pop 2, visit, push its children[3 5 4][1 2]
3pop 4, visit, push its children[3 5][1 2 4]
4pop 5, visit, push its children[3 7][1 2 4 5]
5pop 7, visit, push its children[3][1 2 4 5 7]
6pop 3, visit, push its children[6][1 2 4 5 7 3]
7pop 6, visit, push its children[9 8][1 2 4 5 7 3 6]
8pop 8, visit, push its children[9][1 2 4 5 7 3 6 8]
9pop 9, visit, push its children[][1 2 4 5 7 3 6 8 9]
Step 1 pops 1, writes it, and pushes 3 then 2, leaving [3 2] with 2 on top. Step 2 therefore pops 2, not 3. Node 3 sits at the bottom of the stack untouched through steps 2–5 while the entire left subtree is processed, and only step 6 gets to it. That bottom-of-the-stack waiting is the whole mechanism: the stack is a to-do list of right subtrees. The output is 1 2 4 5 7 3 6 8 9, the same list as the recursive version.

Iterative postorder: the hard one, two ways

Postorder is where answers go wrong, because a node can only be written after both its children are finished, and a plain stack pops a node before you know that. There are two standard fixes. The first is easier to remember and is the one to write on an exam if you are unsure. Two stacks (code in IterativeTraversals.java). Run a preorder-like pass that visits root, then right, then left, but instead of writing nodes, move each one onto a second stack. That second stack now holds the nodes in root-right-left order from bottom to top. Popping it from the top reads them in the reverse order, left-right-root, which is postorder.
public static List<Integer> postorderTwoStacks(TreeNode root, StepListener listener) {
    List<Integer> out = new ArrayList<>();
    if (root == null) {
        return out;
    }
    Deque<TreeNode> first = new ArrayDeque<>();
    Deque<TreeNode> second = new ArrayDeque<>();
    first.push(root);
    while (!first.isEmpty()) {             // phase 1: root-right-left order lands on `second`
        TreeNode node = first.pop();
        second.push(node);
        if (node.left != null) {
            first.push(node.left);
        }
        if (node.right != null) {
            first.push(node.right);
        }
    }
    while (!second.isEmpty()) {            // phase 2: popping reverses it into left-right-root
        TreeNode node = second.pop();
        out.add(node.val);
    }
    return out;
}
The dry run (output/04-postorder-two-stacks-steps.txt). Steps 1–9 are phase one, steps 10–18 are phase two:
stepactions1 (bottom..top)s2 (bottom..top)output so far
1move 1 to s2, push its children on s1[2 3][1][]
2move 3 to s2, push its children on s1[2 6][1 3][]
3move 6 to s2, push its children on s1[2 8 9][1 3 6][]
4move 9 to s2, push its children on s1[2 8][1 3 6 9][]
5move 8 to s2, push its children on s1[2][1 3 6 9 8][]
6move 2 to s2, push its children on s1[4 5][1 3 6 9 8 2][]
7move 5 to s2, push its children on s1[4 7][1 3 6 9 8 2 5][]
8move 7 to s2, push its children on s1[4][1 3 6 9 8 2 5 7][]
9move 4 to s2, push its children on s1[][1 3 6 9 8 2 5 7 4][]
10pop 4 from s2 to output[][1 3 6 9 8 2 5 7][4]
11pop 7 from s2 to output[][1 3 6 9 8 2 5][4 7]
12pop 5 from s2 to output[][1 3 6 9 8 2][4 7 5]
13pop 2 from s2 to output[][1 3 6 9 8][4 7 5 2]
14pop 8 from s2 to output[][1 3 6 9][4 7 5 2 8]
15pop 9 from s2 to output[][1 3 6][4 7 5 2 8 9]
16pop 6 from s2 to output[][1 3][4 7 5 2 8 9 6]
17pop 3 from s2 to output[][1][4 7 5 2 8 9 6 3]
18pop 1 from s2 to output[][][4 7 5 2 8 9 6 3 1]
Two stacks: phase 1 builds a reversed order, phase 2 reads it back outs2 after step 9 (bottom → top)136982574topbottomPopping s2 from the top, one node at a time, gives:output (postorder)475289631Reading s2 from the top is root-right-left reversed, which is left-right-root: exactly postorder.
After step 9 every node has moved to s2 in the order 1 3 6 9 8 2 5 7 4 (root, then right side, then left side, as the table shows). Each pop in phase two takes the top of s2, so output is 4 7 5 2 8 9 6 3 1. The cost is a second stack the size of the whole tree, so this version needs O(n) extra space even on a skewed tree where one stack would do for balanced ones.
Postorder with one stack: the lastVisited trick The single-stack version (IterativeTraversals.java) keeps the stack small (height of the tree) but needs one extra variable. When you reach the top node of the stack you ask: does it have a right child that has not been written yet? If yes, go right first. If no, both sides are done, so pop it and write it. “Not written yet” is tracked by remembering the node written most recently, lastVisited.
public static List<Integer> postorderOneStack(TreeNode root, StepListener listener) {
    List<Integer> out = new ArrayList<>();
    Deque<TreeNode> stack = new ArrayDeque<>();
    TreeNode current = root;
    TreeNode lastVisited = null;           // the node we most recently wrote to the output
    while (current != null || !stack.isEmpty()) {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }
        TreeNode top = stack.peek();
        if (top.right != null && top.right != lastVisited) {
            current = top.right;           // right subtree still unvisited
        } else {
            stack.pop();                   // both children finished (or absent)
            out.add(top.val);
            lastVisited = top;
        }
    }
    return out;
}
The full dry run, 22 steps (output/05-postorder-one-stack-steps.txt):
stepactionstack (bottom..top)output so far
1push 1[1][]
2push 2[1 2][]
3push 4[1 2 4][]
4pop 4, visit[1 2][4]
5peek 2: right child 5 not done, go right[1 2][4]
6push 5[1 2 5][4]
7push 7[1 2 5 7][4]
8pop 7, visit[1 2 5][4 7]
9pop 5, visit[1 2][4 7 5]
10pop 2, visit[1][4 7 5 2]
11peek 1: right child 3 not done, go right[1][4 7 5 2]
12push 3[1 3][4 7 5 2]
13peek 3: right child 6 not done, go right[1 3][4 7 5 2]
14push 6[1 3 6][4 7 5 2]
15push 8[1 3 6 8][4 7 5 2]
16pop 8, visit[1 3 6][4 7 5 2 8]
17peek 6: right child 9 not done, go right[1 3 6][4 7 5 2 8]
18push 9[1 3 6 9][4 7 5 2 8]
19pop 9, visit[1 3 6][4 7 5 2 8 9]
20pop 6, visit[1 3][4 7 5 2 8 9 6]
21pop 3, visit[1][4 7 5 2 8 9 6 3]
22pop 1, visit[][4 7 5 2 8 9 6 3 1]
Look at step 5: node 2 is on top, its right child 5 is not the last node written (that was 4), so the code goes right. At step 10 it pops 2, because by then the last node written is 2’s right child 5, so top.right != lastVisited is false. Drop the lastVisited check and node 2 would send the walk to 5 again and loop forever, which is the classic bug in this algorithm. Source: IterativeTraversals.java.
Morris traversal: inorder with no stack at all Morris traversal (J. M. Morris, 1979; code in IterativeTraversals.java) does inorder in O(1) extra space by borrowing pointers the tree already has. For a node with a left subtree, find the rightmost node of that subtree (the node that inorder visits just before the current one) and make its empty right pointer point back to the current node: a temporary thread. That thread is the way back that a stack would have remembered. When you arrive at the node a second time via the thread, the left side is finished, so remove the thread, write the node, and go right.
Morris: temporary threads (dashed) replace the stack 4 2 7 5 1 3 8 6 9 After step 2 the tree has two temporary right pointers: 5→1 and 4→2.Each one is the “way back” that the stack would have remembered. Each is removed the second time we arrive.Output so far, from the table: [] (nothing is written until the left side is finished).
In the picture, after step 2, the threads 5→1 and 4→2 are in place. The dry run, with a threads column instead of a stack (output/07-morris-inorder-steps.txt):
stepactionthreads (from node)output so far
1at 1: thread 5 -> 1, go left[5][]
2at 2: thread 4 -> 2, go left[5 4][]
3at 4: no left child, visit, go right[5 4][4]
4at 2: thread from 4 found, remove it, visit, go right[5][4 2]
5at 5: thread 7 -> 5, go left[5 7][4 2]
6at 7: no left child, visit, go right[5 7][4 2 7]
7at 5: thread from 7 found, remove it, visit, go right[5][4 2 7 5]
8at 1: thread from 5 found, remove it, visit, go right[][4 2 7 5 1]
9at 3: no left child, visit, go right[][4 2 7 5 1 3]
10at 6: thread 8 -> 6, go left[8][4 2 7 5 1 3]
11at 8: no left child, visit, go right[8][4 2 7 5 1 3 8]
12at 6: thread from 8 found, remove it, visit, go right[][4 2 7 5 1 3 8 6]
13at 9: no left child, visit, go right[][4 2 7 5 1 3 8 6 9]
The method, from IterativeTraversals.java:
public static List<Integer> morrisInorder(TreeNode root, StepListener listener) {
    List<Integer> out = new ArrayList<>();
    List<Integer> threads = new ArrayList<>();
    TreeNode current = root;
    while (current != null) {
        if (current.left == null) {
            out.add(current.val);
            current = current.right;
        } else {
            TreeNode predecessor = current.left;
            while (predecessor.right != null && predecessor.right != current) {
                predecessor = predecessor.right;
            }
            if (predecessor.right == null) {
                predecessor.right = current;   // create the thread
                threads.add(predecessor.val);
                current = current.left;
            } else {
                predecessor.right = null;      // thread found: left side finished, remove it
                threads.remove(Integer.valueOf(predecessor.val));
                out.add(current.val);
                current = current.right;
            }
        }
    }
    return out;
}
The last two lines of the transcript show the tree before and after: identical, because every thread is removed. The test suite asserts that on 1,000 random trees. Source: IterativeTraversals.java.
The catch: while Morris runs, the tree is temporarily modified. If another thread reads the tree at the same time, or an exception interrupts the loop, readers see the extra pointers (or the tree is left corrupted). That is why it is an interview curiosity and not what you ship. Time is still O(n): each edge is followed a constant number of times.

Going deeper

  • The sample-tree results of every variant, side by side: output/01-sample-tree-all-traversals.txt.
  • Morris preorder is the same loop but writes the node the first time it is reached, when the thread is created: morrisPreorder in the same file.
  • Original paper: J. M. Morris, “Traversing binary trees simply and cheaply”, Information Processing Letters 9(5), 1979.

Level order: swap the stack for a queue

Depth-first goes as deep as possible before coming back. Level order (breadth-first) goes row by row: all nodes at level 0, then level 1, and so on. The only change from iterative preorder is the container. A stack returns the newest item; a queue returns the oldest, so children join the back of the line and are served after everything already waiting, which is exactly row-by-row order. In Java the queue is an ArrayDeque used through offer and poll (LevelOrder.java).
public static List<Integer> levelOrder(TreeNode root, StepListener listener) {
    List<Integer> out = new ArrayList<>();
    if (root == null) {
        return out;
    }
    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        out.add(node.val);
        if (node.left != null) {
            queue.offer(node.left);
        }
        if (node.right != null) {
            queue.offer(node.right);
        }
    }
    return out;
}
The dry run (output/06-level-order-queue-steps.txt); the front of the queue is the left end:
stepactionqueue (front..back)output so far
1poll 1, visit, offer its children[2 3][1]
2poll 2, visit, offer its children[3 4 5][1 2]
3poll 3, visit, offer its children[4 5 6][1 2 3]
4poll 4, visit, offer its children[5 6][1 2 3 4]
5poll 5, visit, offer its children[6 7][1 2 3 4 5]
6poll 6, visit, offer its children[7 8 9][1 2 3 4 5 6]
7poll 7, visit, offer its children[8 9][1 2 3 4 5 6 7]
8poll 8, visit, offer its children[9][1 2 3 4 5 6 7 8]
9poll 9, visit, offer its children[][1 2 3 4 5 6 7 8 9]
The queue after each step (front on the left)frontstep 1: poll 123step 2: poll 2345step 3: poll 3456step 4: poll 456step 5: poll 567step 6: poll 6789Nodes of one level are always polled before any node of the next level, because they entered the queue first.
Each step takes the node at the front, writes it, and adds its children at the back. After step 3 the queue is [4 5 6]: the entire next level, with no level-1 node left. The queue never held more than three nodes here, the width of the widest level; that width, not the height, is what a queue-based traversal pays in memory.
Level order (queue) 4 4 2 2 7 7 5 5 1 1 3 3 8 8 6 6 9 9Order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9Level order reads the tree row by row, left to right.Here the badges equal the values only because the sample values were chosen in level order; on other trees they differ.
One list per level (LevelOrder.java). Interviewers usually ask for a list of lists. The trick is to freeze queue.size() before the inner loop: that number is exactly how many nodes belong to the current level, because everything added during the loop belongs to the next one.
public static List<List<Integer>> byLevel(TreeNode root) {
    List<List<Integer>> levels = new ArrayList<>();
    if (root == null) {
        return levels;
    }
    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int sizeOfThisLevel = queue.size();
        List<Integer> level = new ArrayList<>(sizeOfThisLevel);
        for (int i = 0; i < sizeOfThisLevel; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        levels.add(level);
    }
    return levels;
}
Zigzag (LevelOrder.java). Same loop, but alternate the direction in which each level is written: left to right, then right to left. The queue still fills left to right; only how each level’s list is assembled changes, using addFirst on odd levels.
public static List<List<Integer>> zigzag(TreeNode root) {
    List<List<Integer>> levels = new ArrayList<>();
    if (root == null) {
        return levels;
    }
    Deque<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    boolean leftToRight = true;
    while (!queue.isEmpty()) {
        int sizeOfThisLevel = queue.size();
        Deque<Integer> level = new ArrayDeque<>();
        for (int i = 0; i < sizeOfThisLevel; i++) {
            TreeNode node = queue.poll();
            if (leftToRight) {
                level.addLast(node.val);
            } else {
                level.addFirst(node.val);
            }
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        levels.add(new ArrayList<>(level));
        leftToRight = !leftToRight;
    }
    return levels;
}
Results on the sample tree (output/01-sample-tree-all-traversals.txt):
level order (queue)       : [1, 2, 3, 4, 5, 6, 7, 8, 9]
level order by level      : [[1], [2, 3], [4, 5, 6], [7, 8, 9]]
zigzag                    : [[1], [3, 2], [4, 5, 6], [9, 8, 7]]
Zigzag: alternate the direction on every level 4 4 2 3 7 9 5 5 1 1 3 2 8 8 6 6 9 7→level 0←level 1→level 2←level 3Output: 1 | 3 2 | 4 5 6 | 9 8 7The queue still fills left to right; only the way each level is written into its list flips.
In the zigzag picture the badges follow the output 1 | 3 2 | 4 5 6 | 9 8 7: level 1 is written right to left (3 then 2), level 2 left to right, and level 3 right to left again (9 8 7).

Going deeper

Height and diameter: the O(n) answer and the O(n²) trap

Height is the easy one: the height of an empty tree is 0, and the height of any other tree is 1 plus the larger of its two subtree heights. This page counts in nodes (a single node has height 1). Many textbooks count edges, where a single node has height 0; subtract one to convert. (TreeMetrics.java)
public static int height(TreeNode node) {
    if (node == null) {
        return 0;
    }
    return 1 + Math.max(height(node.left), height(node.right));
}
Diameter is the number of nodes on the longest path between any two nodes. That path does not have to pass through the root, which is the usual mistake. The sample tree, with its subtree heights and one longest path, drawn from output/08-height-diameter-sample.txt:
Diameter: the longest path between any two nodes 4 1 2 3 7 1 5 2 1 4 3 3 8 1 6 2 9 1Path: 7 → 5 → 2 → 1 → 3 → 6 → 8 (7 nodes, 6 edges)Green badge = height of the subtree rooted at that node.Best path through a node = left height + right height + 1. At node 1: 3 + 3 + 1 = 7.A path ending at 9 instead of 8 is equally long; the code prefers the left child on ties.
The path 7, 5, 2, 1, 3, 6, 8 has 7 nodes and 6 edges. Every path has a highest node, and the best path through that node is its left height plus its right height plus 1. So the diameter is the maximum of that quantity over all nodes. Here is the program output behind the picture (output/08-height-diameter-sample.txt):
nodes                         : 9
leaves                        : 4
height, counted in nodes      : 4
height, counted in edges      : 3
height via level order (BFS)  : 4
diameter, counted in nodes    : 7
diameter, counted in edges    : 6
one longest path              : [7, 5, 2, 1, 3, 6, 8]
height of the subtree at node : {1=4, 2=3, 3=3, 4=1, 5=2, 6=2, 7=1, 8=1, 9=1}
The obvious code is quadratic. “For every node, compute the height of both sides, then recurse into both children” is correct and slow: computing a height walks a whole subtree, and it is repeated at every node. The fix is to compute height and diameter in the same single postorder pass: each node returns its height and, as a side effect, updates the best diameter seen. Both versions are in TreeMetrics.java, with a counter that tallies every node entered:
public static int diameterNaive(TreeNode node, long[] visits) {
    if (node == null) {
        return 0;
    }
    visits[0]++;
    int through = heightCounting(node.left, visits) + heightCounting(node.right, visits) + 1;
    int inLeft = diameterNaive(node.left, visits);
    int inRight = diameterNaive(node.right, visits);
    return Math.max(through, Math.max(inLeft, inRight));
}

private static int heightCounting(TreeNode node, long[] visits) {
    if (node == null) {
        return 0;
    }
    visits[0]++;
    return 1 + Math.max(heightCounting(node.left, visits), heightCounting(node.right, visits));
}
public static int diameterOnePass(TreeNode root, long[] visits) {
    int[] best = {0};
    heightAndDiameter(root, best, visits);
    return best[0];
}

private static int heightAndDiameter(TreeNode node, int[] best, long[] visits) {
    if (node == null) {
        return 0;
    }
    visits[0]++;
    int left = heightAndDiameter(node.left, best, visits);
    int right = heightAndDiameter(node.right, best, visits);
    best[0] = Math.max(best[0], left + right + 1);
    return 1 + Math.max(left, right);
}
A counted comparison, not a timing (output/09-diameter-visit-counts.txt). The sample tree and three tree shapes, naive visits against one-pass visits:
tree                      nodes   naive visits     one-pass   diameter
sample (9)                    9             26            9          7
perfect, 4 levels            15             49           15          7
perfect, 7 levels           127            769          127         13
perfect, 10 levels         1023           9217         1023         19
left-skewed 100             100           5050          100        100
left-skewed 1000           1000         500500         1000       1000
left-skewed 2000           2000        2001000         2000       2000
Counted node visits, naive vs one pass (each pair scaled to its own naive bar)perfect, 10 levels: 1023 nodesnaive: 9,217one pass: 1,023left-skewed 1000: 1000 nodesnaive: 500,500one pass: 1,000left-skewed 2000: 2000 nodesnaive: 2,001,000one pass: 2,000The one-pass bar is the node count itself. On the 2000-node chain the naive bar is about 1,000 times longer.
The one-pass count equals the node count in every row: each node is entered exactly once. The naive count depends on the shape. On a skewed chain it is exactly n(n+1)/2, the sum 1 + 2 + … + n, which gives 500,500 for 1,000 nodes and 2,001,000 for 2,000: double the nodes, four times the work. On a balanced tree it grows much more gently, about 3.3, 6.1 and 9.0 visits per node for the 4-, 7- and 10-level perfect trees (these are just the printed counts divided by n), which is O(n log n). So the naive solution looks fine on the textbook picture and falls over on a skewed input.
Check the convention before you answer. The diameter of the sample tree is 7 if you count nodes and 6 if you count edges; its height is 4 nodes or 3 edges. LeetCode 543 (“Diameter of Binary Tree”) counts edges, so it expects 6 for this tree; many university papers count nodes. The algorithm is the same and the answer differs by one, so state which one you used.

Going deeper

  • Height without recursion: the number of levels a queue traversal passes through, heightIterative in TreeMetrics.java. The tests check it against the recursive height on 1,000 random trees.
  • The tests also check the diameter against an independent brute force (BFS from every node over the tree seen as an undirected graph), not only against the other implementation.

Rebuilding a tree from two traversals

The classic exam question: “the inorder is 4 2 5 1 6 3 and the preorder is 1 2 4 5 3 6; draw the tree”. The method is always the same three moves. Preorder starts with the root. Find that root in the inorder list: everything to its left is the left subtree, everything to its right is the right subtree. Recurse on each side, using the next preorder values for the left part and then the right part.
Step one of construction: preorder names the root, inorder splits the restpreorder (root, left part, right part)124536inorder (left part, root, right part)425163Root = first preorder value = 1. Find 1 in inorder: 3 values sit to its left, 2 to its right.So the next 3 preorder values form the left subtree and the last 2 form the right subtree. Recurse on each pair.Blue = left subtree, green = right subtree, amber = root. The recursion trace is in output/10-construct-from-traversals.txt.
The diagram is the first step. Root 1; inorder puts three values (4, 2, 5) on its left and two (6, 3) on its right, so the next three preorder values (2, 4, 5) are the left subtree and the last two (3, 6) the right. The code does exactly this (Trees.java); a hash map from value to inorder position makes each split O(1), so the whole build is O(n).
private static TreeNode buildPre(int[] pre, int[] next, int lo, int hi, Map<Integer, Integer> position,
                                 Consumer<String> trace, int depth) {
    if (lo > hi) {
        return null;
    }
    int rootValue = pre[next[0]++];
    int mid = position.get(rootValue);
    TreeNode node = new TreeNode(rootValue);
    node.left = buildPre(pre, next, lo, mid - 1, position, trace, depth + 1);
    node.right = buildPre(pre, next, mid + 1, hi, position, trace, depth + 1);
    return node;
}
Every recursive call, printed by the real code through a trace callback (output/10-construct-from-traversals.txt); indentation is depth in the tree:
inorder   : [4, 2, 5, 1, 6, 3]
preorder  : [1, 2, 4, 5, 3, 6]
root 1  inorder slice [0..5]  left size 3, right size 2
  root 2  inorder slice [0..2]  left size 1, right size 1
    root 4  inorder slice [0..0]  left size 0, right size 0
    root 5  inorder slice [2..2]  left size 0, right size 0
  root 3  inorder slice [4..5]  left size 1, right size 0
    root 6  inorder slice [4..4]  left size 0, right size 0
result    : 1(2(4,5),3(6,-))
postorder of the result: [4, 5, 2, 6, 3, 1]
The result is 1(2(4,5),3(6,-)) and its postorder is 4 5 2 6 3 1. The same tree also comes back from inorder plus postorder; the only change is that the root is the last postorder value and the right subtree must be built first.
Preorder plus postorder is not enough. Inorder is what separates “left” from “right”. Without it, a node with a single child is ambiguous. The program (output/10-construct-from-traversals.txt) shows two different trees with identical preorder and identical postorder:

  1(2,-)  preorder [1, 2]  postorder [2, 1]
  1(-,2)  preorder [1, 2]  postorder [2, 1]

Complexity and which traversal to pick

TraversalTimeExtra spaceUse it when
Recursive pre/in/postO(n)O(h) call stackTree is known to be shallow (balanced)
Iterative with a stackO(n)O(h) on the heapDepth is not under your control
Postorder, two stacksO(n)O(n)You need it simple and quick to write
MorrisO(n)O(1)Memory is critical and nobody else reads the tree
Level orderO(n)O(w), w = widest levelYou need rows, shortest depth, or zigzag
h is the height (about log n when balanced, n when skewed). Which order for which job: preorder to copy or serialise a tree (parent before children); inorder to read a BST in sorted order; postorder to delete a tree or evaluate an expression tree (children before parent); level order for anything about rows or nearest-to-root.
How the tests compare every implementation The page quotes outputs for one tree; the test suite checks every implementation against the others on 1,000 random trees each (sizes 0–79, values a random permutation of 1..n). Recursive, iterative, two-stack, one-stack and Morris traversals must agree, Morris must restore the tree, and trees rebuilt from their own traversals must match the originals. The first test, from RandomTreesEquivalenceTest.java:
void allDepthFirstImplementationsAgreeOnRandomTrees() {
    Random random = new Random(20261106L);
    for (int round = 0; round < 1000; round++) {
        int n = random.nextInt(80);
        TreeNode root = Trees.random(random, n);
        String shape = Trees.bracket(root);

        List<Integer> in = RecursiveTraversals.inorder(root);
        List<Integer> pre = RecursiveTraversals.preorder(root);
        List<Integer> post = RecursiveTraversals.postorder(root);
        assertEquals(n, in.size());

        assertEquals(in, IterativeTraversals.inorder(root), shape);
        assertEquals(pre, IterativeTraversals.preorder(root), shape);
        assertEquals(post, IterativeTraversals.postorderTwoStacks(root), shape);
        assertEquals(post, IterativeTraversals.postorderOneStack(root), shape);
        assertEquals(in, IterativeTraversals.morrisInorder(root), shape);
        assertEquals(shape, Trees.bracket(root), "Morris inorder must restore the tree");
        assertEquals(pre, IterativeTraversals.morrisPreorder(root), shape);
        assertEquals(shape, Trees.bracket(root), "Morris preorder must restore the tree");

        // construction round trips
        TreeNode a = Trees.fromInorderPreorder(Trees.toArray(in), Trees.toArray(pre), null);
        TreeNode b = Trees.fromInorderPostorder(Trees.toArray(in), Trees.toArray(post));
        assertEquals(shape, Trees.bracket(a));
        assertEquals(shape, Trees.bracket(b));
    }
}
Run everything with mvn test in the binary-tree-traversals module (15 tests); scripts/run-all.sh also regenerates the three stack-overflow measurements.

Exam-style questions with answers

Try each one on paper first, then open the answer. All numbers below are from the sample tree and the output files linked above.
Q1. Write the preorder, inorder, postorder and level-order traversals of the sample tree.

Preorder: 1 2 4 5 7 3 6 8 9. Inorder: 4 2 7 5 1 3 8 6 9. Postorder: 4 7 5 2 8 9 6 3 1. Level order: 1 2 3 4 5 6 7 8 9. Source: output/01-sample-tree-all-traversals.txt.

Method: preorder writes the root first, so 1 then the entire left subtree (2, 4, 5, 7) then the right subtree (3, 6, 8, 9). The same walk with the root written last gives postorder.

Q2. The inorder is 4 2 5 1 6 3 and the preorder is 1 2 4 5 3 6. Construct the tree and give its postorder.

Tree 1(2(4,5),3(6,-)): root 1; left child 2 with children 4 and 5; right child 3 whose only child is a left child 6. Postorder: 4 5 2 6 3 1 (output/10-construct-from-traversals.txt).

Why 6 is a left child: inorder is 6 3, and 6 is written before 3, so 6 is in 3’s left subtree.

Q3. Count the nodes and the leaves. How are leaves and two-child nodes related?

The sample tree has 9 nodes and 4 leaves (output/08-height-diameter-sample.txt). Counting recursively: a null tree has 0 nodes; otherwise 1 plus the counts of both subtrees; a leaf is a node with two null children.

For any binary tree, leaves = (nodes with two children) + 1. In the sample tree the nodes with two children are 1, 2 and 6, which is 3, and 3 + 1 = 4 leaves.

Q4. What are the height and the diameter of the sample tree?

Height 4 counted in nodes, 3 counted in edges. Diameter 7 counted in nodes, 6 counted in edges; one longest path is 7, 5, 2, 1, 3, 6, 8 (output/08-height-diameter-sample.txt). Say which convention you use. The diameter here does pass through the root, but it need not in general.

Q5. Which traversal produces sorted output for a binary search tree, and does it work for any binary tree?

Inorder produces sorted (ascending) output for a BST, shown in output/11-bst-inorder-sorted.txt. It does not for an arbitrary binary tree: the inorder of the sample tree is 4 2 7 5 1 3 8 6 9.

Q6. Can a tree be reconstructed from preorder and postorder alone?

Not in general. If every node has either 0 or 2 children the answer is yes, but with single-child nodes the left and right case give the same two sequences: the trees 1(2,-) and 1(-,2) both have preorder 1 2 and postorder 2 1 (output/10-construct-from-traversals.txt). Inorder plus either preorder or postorder is always enough, provided the values are distinct.

Q7. What is the maximum number of nodes in a binary tree of height h?

2h − 1 when height counts levels (root level = 1), because level k from the top (k starting at 0) holds at most 2k nodes. The perfect trees in output/09-diameter-visit-counts.txt confirm it: 4 levels give 15 nodes, 7 levels 127 and 10 levels 1023.

The reverse is why balanced trees are shallow: a million nodes fit in 20 levels.

Q8. What is the space complexity of recursive inorder on a balanced tree and on a skewed tree?

O(h) for the call stack: about O(log n) when balanced, O(n) when skewed. The output list itself is O(n) in both. On the skewed tree of 1,000,000 nodes the recursion overflowed after a depth in the tens of thousands, while the iterative version completed (output/12-deep-recursion-default-stack.txt).

Should you hand-write these in production? Almost never. If you need a sorted structure, TreeMap and TreeSet are balanced red-black trees whose iterators already walk in inorder and cannot degenerate into the skewed chain from the overflow section. Hand-written traversals earn their place in interviews, in exams, and in the rare case of your own tree structure, such as a parser’s AST or a decision tree. For those, prefer the iterative stack versions when the depth depends on input you do not control, and keep recursion for trees you know are shallow. Morris is worth understanding and rarely worth shipping.

Further reading

No Comments yet!

This site uses Akismet to reduce spam. Learn how your comment data is processed.