← All Posts
DSA Series · Trees · Threaded · Part 4

Morris Traversal

In the previous post, we saw how a permanently threaded tree achieves O(1)-space in-order traversal. The threads, pointers from nodes with no right child to their in-order successor, eliminate the need for a stack entirely. But permanently threading a tree requires modifying every node (adding boolean flags), building the threads upfront, and maintaining them across insertions and deletions.

Morris traversal asks a radical question: what if we could get O(1)-space traversal on a completely standard, unmodified binary tree, no extra boolean flags, no upfront construction, no permanent changes?

The answer is yes. Morris traversal creates threads on the fly, uses them to navigate the tree, and then removes them, leaving the tree in its original state. It achieves O(n) time and O(1) space without ever building a "threaded tree" in the formal sense.

The Fundamental Insight

Why Does Recursion Need a Stack?

Say we're doing in-order traversal and we arrive at some node X that has a left subtree. We go left, and keep going deeper into that subtree until we've visited every node in it. The very last node we visit in that left subtree is the rightmost node, let's call it P.

Wait, What is an In-Order Predecessor?

The in-order predecessor of a node is simply the node that comes right before it in an in-order traversal. In-order means: left subtree, then the node itself, then right subtree. So the predecessor of X is the last node visited before X.

If X has a left subtree, that predecessor lives inside it. Specifically, it's the rightmost node in the left subtree. Why? Because in-order traversal visits the left subtree in L-N-R order, and the very last thing it touches before leaving a subtree is the rightmost node (it keeps going right until it can't anymore).

Here's a concrete example:

20 X 10 5 15 12 25 go right go left (no right child) P (predecessor of 20) In-order: 5, 10, 12, 20, 25 left subtree of 20
The in-order predecessor of 20 is 12. It's the rightmost node in 20's left subtree: go left to 10, right to 15, then left to 12 (15 has no right child, so 12 is as far right as we get). In the in-order sequence, 12 sits immediately before 20.

To find the predecessor: start at X's left child, then keep going right until you can't. That final node is P. It's the last node in-order visits before returning to X, which is exactly why it's the perfect place to plant a thread back to X.

X left subtree P rightmost node right subtree go left...
We descend left from X into the left subtree. P is the last node we visit there.

Now we're done with the left subtree. We need to come back up to X so we can visit it and then explore its right subtree. But here's the problem: P has no pointer leading back to X. In a regular binary tree, child nodes don't know about their ancestors.

X need to get back here P null no pointer back!
P is stuck. Its right pointer is null. There is no link back up to X.

So how does recursion handle this? Before going left from X, it saves X on the call stack. When the left subtree is fully done, it pops X off the stack and picks up where it left off. That stack grows as tall as the tree, costing O(h) extra space.

X go left left subtree STACK X ... push X O(h) space
Recursion pushes X onto the stack before going left. This costs O(h) memory.

The Threading Trick

Now recall what we learned about threaded trees: in a right-threaded BST, P's right pointer doesn't go to a child, it threads directly to X. That thread eliminates the need for a stack at that step.

X P thread! right No stack needed!
In a threaded tree, P's right pointer links directly back to X. No stack required.

Morris's insight: we don't need the tree to be permanently threaded. We can create this one specific thread, from the predecessor P to the current node X, right before we need it. Once we've used the thread to return to X, we remove it. The tree is temporarily modified, but it's restored to its original shape.

1. Create thread X P 2. Use thread X P follow back to X 3. Remove thread X P tree restored ✓
The three-step cycle: plant the thread, follow it back, then clean it up. Tree ends up unchanged.
In one sentence: Morris traversal simulates the effect of a threaded tree by creating and destroying threads on demand, one at a time, using zero extra space beyond a couple of pointer variables.

Why This Needs No Extra Storage

In a threaded binary tree, we need boolean flags (leftThread, rightThread) to distinguish threads from real child pointers. Morris traversal doesn't need these flags because it uses a different mechanism to tell threads apart from children: it checks whether the predecessor's right pointer points back to the current node.

First visit P null P->right is null → Create thread Second visit P X P->right points to X → Remove thread No boolean flags needed. The pointer itself tells us.
Morris uses P's right pointer value to distinguish first visits from returns. No extra flags.

When we're at node X and find the predecessor P:

The predecessor's right pointer itself carries the information that boolean flags would carry in a permanent threaded tree. No extra storage needed.

The Algorithm, Step by Step

We maintain a single pointer cur, starting at the root. At each iteration:

Case 1: cur has no left child

There's no left subtree to explore. We visit cur (it's the next node in in-order) and move right: cur = cur->right.

If cur->right is a thread we created earlier, this takes us back up to the ancestor that created the thread. If it's a real child pointer, it takes us into the right subtree. Either way, we end up at the correct next node.

Case 2: cur has a left child

We need to traverse the left subtree before visiting cur. But first, we need a way to get back to cur after the left subtree is done. So we find the in-order predecessor of cur, the rightmost node in the left subtree.

Finding the predecessor

TreeNode* pred = cur->left;
while (pred->right && pred->right != cur)
    pred = pred->right;

We go one step left, then keep going right until we hit either null (the rightmost node) or cur itself (a thread we created earlier). The condition pred->right != cur is critical, it prevents us from looping forever on a thread we already set.

Sub-case 2a: pred->right == nullptr (first visit)

We've never been here before. The predecessor's right pointer is genuinely null. This is our moment to plant the thread:

pred->right = cur;   // create temporary thread
cur = cur->left;     // descend into left subtree

The thread is our breadcrumb. When the left subtree traversal eventually reaches pred and tries to go right, it will follow this thread back to cur.

We do NOT visit cur yet. In in-order traversal, we visit the left subtree first. We'll come back to cur later via the thread.

Sub-case 2b: pred->right == cur (second visit)

The predecessor's right pointer already points to cur. This means we created this thread earlier, descended into the left subtree, traversed it completely, and have now returned to cur via the thread. The left subtree is done.

pred->right = nullptr;  // remove the thread (restore original tree)
visit(cur);             // NOW visit cur (left subtree is complete)
cur = cur->right;       // move to right subtree

We remove the thread to restore the tree, visit the node (in-order: left subtree done, now the root), and proceed to the right subtree.

The key invariant: Every node with a left subtree is encountered exactly twice. The first time, we thread and go left. The second time, we unthread, visit, and go right. Nodes without a left child are encountered once, we visit and go right immediately.

The Complete Code

void morrisInorder(TreeNode* root) {
    TreeNode* cur = root;

    while (cur) {
        if (!cur->left) {
            // ---- CASE 1 ----
            // No left subtree. This node is next in in-order.
            visit(cur);
            cur = cur->right;  // go right (may follow a thread)
        } else {
            // ---- CASE 2 ----
            // Has left subtree. Find in-order predecessor.
            TreeNode* pred = cur->left;
            while (pred->right && pred->right != cur)
                pred = pred->right;

            if (!pred->right) {
                // ---- Sub-case 2a: First visit ----
                // Create thread and descend left.
                pred->right = cur;
                cur = cur->left;
            } else {
                // ---- Sub-case 2b: Second visit ----
                // Left subtree done. Remove thread, visit, go right.
                pred->right = nullptr;
                visit(cur);
                cur = cur->right;
            }
        }
    }
}

That's the entire algorithm. No stack. No recursion. No boolean flags. No auxiliary data structure. Just a while loop with one pointer variable.

Morris vs. Threaded Tree vs. Stack: Full Comparison

Recursive / Stack Threaded Tree Morris Traversal
Time O(n) O(n) O(n)
Space O(h) stack O(1) traversal
(+ 2 bools/node permanent)
O(1) total
Extra per-node storage None 2 boolean flags None
Setup cost None O(n) to build threads None
Modifies tree? No Once (during construction) Temporarily (during traversal)
Concurrent reads? Safe Safe (after construction) Not safe
Works on const tree? Yes Yes (after construction) No
Exception-safe? Yes Yes No (tree left broken)
Repeated traversals O(n) each time O(n) each, no re-threading O(n) each, must re-thread
Best for General use, safety Many traversals, iterators One-shot O(1) space need

Morris Preorder Variant

The in-order version visits nodes on the second encounter (after the left subtree is done). For preorder, we want to visit on the first encounter (before descending left). The change is minimal:

void morrisPreorder(TreeNode* root) {
    TreeNode* cur = root;
    while (cur) {
        if (!cur->left) {
            visit(cur);             // no left subtree: visit and go right
            cur = cur->right;
        } else {
            TreeNode* pred = cur->left;
            while (pred->right && pred->right != cur)
                pred = pred->right;

            if (!pred->right) {
                visit(cur);         // VISIT on first encounter (preorder)
                pred->right = cur;  // create thread
                cur = cur->left;    // descend left
            } else {
                pred->right = nullptr;  // remove thread
                // DON'T visit (already visited on first encounter)
                cur = cur->right;
            }
        }
    }
}

The only difference: visit(cur) moves from sub-case 2b to sub-case 2a. Everything else, thread creation, removal, cursor movement, stays identical.

Morris Postorder Variant

Post-order is where Morris traversal gets genuinely interesting. The trick itself is elegant: introduce a dummy node, and on every second encounter, reverse-visit the right boundary of the left subtree before moving on. The idea fits in a paragraph, but understanding why it works, how the pointer reversal preserves O(1) space, what the dummy node actually solves, and how each step maps to the final output requires careful, visual explanation.

It deserves its own dedicated treatment rather than being compressed into a subsection here.

Continue reading: Morris Post-order Traversal — full visual walkthrough with animated diagrams for every step, the reverse-visit trick broken down frame by frame, and an interactive step-through animation.

When to Use Morris Traversal

Use Morris when:

Don't use Morris when:

Interactive Animations

▶ Morris In-order Traversal

Visit: left subtree → node → right subtree. Expected output: 1, 2, 3, 4, 5, 6.

4 2 6 1 3 5

▶ Morris Pre-order Traversal

Visit: node → left subtree → right subtree. Expected output: 4, 2, 1, 3, 6, 5.

4 2 6 1 3 5

▶ Morris Post-order Traversal

Visit: left subtree → right subtree → node. Uses a dummy node (D). Expected output: 1, 3, 2, 5, 6, 4.

D 4 2 6 1 3 5

Summary