Morris Traversal — Stop Stack Overflow in Skewed Trees
A sensor node crashed because recursion depth matched node count on a skewed tree.
- Morris Traversal walks a binary tree without recursion or stack using temporary right-pointer threads
- Each left subtree's rightmost node gets a thread back to the current node — that's your bookmark
- Creating and removing each thread costs O(1) amortized; total time is O(n), space is O(1)
- Every thread is removed before traversal ends — the tree is fully restored
- The biggest mistake: forgetting that threads modify the tree — unsafe for concurrent reads without locks
Imagine you're hiking through a forest maze with no map and no pen to mark where you've been. Morris Traversal is like secretly bending a twig on your way past each junction — so when you loop back, you know you've already visited that spot and can straighten it out behind you. You never carry a backpack of notes (no stack, no recursion). You just temporarily 'thread' the path behind you, use it, then restore the forest exactly as you found it.
Every serious DSA practitioner hits a wall when they realise that recursive tree traversal — elegant as it is — carries a hidden O(h) space cost on the call stack, where h is the tree height. On a skewed tree with a million nodes, that's a million stack frames. In memory-constrained environments — embedded systems, high-throughput stream processors, certain competitive programming judges — that cost is unacceptable. Morris Traversal, published by Joseph Morris in 1979, solves this with an audacious idea: temporarily rewrite the tree's own null pointers to encode traversal state, then undo every change before you leave.
The problem Morris Traversal solves is surprisingly fundamental: how do you know where to 'go back' after finishing a left subtree, without a stack keeping that address for you? The classic iterative approach just swaps the implicit call stack for an explicit one — same asymptotic cost, different constant. Morris's insight was that null right-child pointers in inorder predecessors are wasted space. There are exactly n-1 edges in a binary tree, leaving n+1 null pointers sitting idle. He used a subset of those to store temporary back-links — called 'threads' — that guide traversal back up the tree without any auxiliary structure.
By the end of this article you'll be able to implement Morris Inorder and Morris Preorder from scratch, explain the thread-creation and thread-detection logic in an interview, reason about exactly when the tree is in a temporarily modified state (and why that matters for concurrent code), and confidently handle every edge case — single nodes, skewed trees, complete binary trees, and trees with duplicate values.
What is Morris Traversal? — Plain English
Morris Traversal performs in-order traversal of a binary tree using O(1) extra space — no recursion stack, no auxiliary stack. The trick is to temporarily modify the tree by creating 'threads': right pointers that point back to the in-order successor. Normal in-order traversal uses a call stack (O(h) space) to remember where to return after finishing the left subtree. Morris Traversal avoids this by using the rightmost node of each left subtree as a bookmark — it threads a right pointer from that node back to the current node, allowing the traversal to return without a stack. After using the thread, it is removed to restore the original tree. The tree ends up unchanged after the traversal completes.
How Morris Traversal Works — Step by Step
Algorithm for in-order Morris Traversal:
- Set current = root.
- While current is not None:
- a. If current has no left child: visit current (append to result), then current = current.right.
- b. If current has a left child:
- i. Find the in-order predecessor: the rightmost node of current.left subtree (predecessor = current.left, then go right until right is None OR right == current).
- ii. If predecessor.right == None (no thread yet): create thread: predecessor.right = current. Move current = current.left.
- iii. If predecessor.right == current (thread exists, we're returning): remove thread: predecessor.right = None. Visit current. Move current = current.right.
- When current becomes None, traversal is complete.
Worked Example — Tracing Morris Traversal on a Tree
Tree: 4 / \ 2 5 / \ 1 3
In-order should produce: [1,2,3,4,5].
current=4. Has left. Predecessor of 4 = rightmost of 2's subtree = node 3 (3.right=None). Create thread: 3.right=4. Move current=2. current=2. Has left. Predecessor of 2 = rightmost of 1's subtree = node 1 (1.right=None). Create thread: 1.right=2. Move current=1. current=1. No left. Visit 1 → result=[1]. current=1.right=2 (thread). current=2. Has left. Predecessor of 2 = node 1. 1.right=2 (thread exists). Remove thread: 1.right=None. Visit 2 → result=[1,2]. current=2.right=3. current=3. No left. Visit 3 → result=[1,2,3]. current=3.right=4 (thread). current=4. Has left. Predecessor of 4 = node 3. 3.right=4 (thread exists). Remove thread: 3.right=None. Visit 4 → result=[1,2,3,4]. current=4.right=5. current=5. No left. Visit 5 → result=[1,2,3,4,5]. current=5.right=None. Done.
Implementation
Morris Traversal modifies right pointers temporarily as threads, then restores them. Finding each predecessor takes O(1) amortized time because each edge is traversed at most twice (once to create the thread, once to remove it), giving O(n) total. Space is O(1) — only a few pointers are needed. The tradeoff versus recursive DFS is increased code complexity and the temporary mutation of the tree during traversal.
Complexity Analysis & Trade-offs
Morris Traversal runs in O(n) time and O(1) extra space. The constant factor is higher than recursive or iterative stack approaches because each node's predecessor search may traverse several edges. However, amortised analysis shows each edge is traversed at most twice: once when creating the thread and once when removing it. The total number of pointer assignments is 2*(n-1) = O(n).
- Recursive: O(n) time, O(h) stack space (h = height, worst-case O(n)).
- Iterative with explicit stack: O(n) time, O(h) space (similar, but stack allocated on heap).
- Morris: O(n) time, O(1) extra space — but mutates the tree during traversal.
- Stack space is extremely limited (embedded, kernel).
- You cannot allocate dynamic memory (real-time systems).
- The interviewer explicitly asks for O(1) space solution.
- The tree is shared with concurrent readers — mutation causes race conditions.
- The tree is read-only (you'd need to copy it first).
- Code clarity is more important than memory (Morris is harder to read/debug).
Morris Preorder Traversal
The same threading idea works for preorder traversal with a slight modification: visit the node before creating the thread (or before descending into the left subtree). In inorder, you visit after removing the thread. In preorder, you visit the current node as soon as you encounter it, then create the thread and descend.
Algorithm: 1. current = root. 2. While current != None: a. If current.left is None: visit(current) current = current.right b. Else: pred = current.left while pred.right and pred.right is not current: pred = pred.right if pred.right is None: visit(current) // visit before creating thread pred.right = current current = current.left else: pred.right = None current = current.right
The key difference: in preorder, you visit the node when you first reach it (before any thread creation). In inorder, you visit after removing the thread (when returning from left subtree).
Memory-Constrained Sensor Node Crashed During Recursive Tree Walk
- Never assume tree structure. Always handle worst-case skewness.
- In memory-constrained environments, Morris Traversal is safer than recursion or even explicit stacks.
- Always profile stack usage — a single recursive call can hide catastrophic depth.
Key takeaways
Common mistakes to avoid
4 patternsForgetting the second condition in predecessor search
Placing the visit at the wrong point in the algorithm
Not restoring the tree after traversal (thread leak)
Using Morris Traversal on a tree that is being concurrently modified
Interview Questions on This Topic
Explain how Morris Traversal achieves O(1) space for in-order traversal.
Frequently Asked Questions
That's Trees. Mark it forged?
4 min read · try the examples if you haven't