Algorithm

Morris Traversal (Inorder)

Trees Pattern

Morris Traversal (Inorder)

Produce the in-order sequence of a binary tree's values using O(1) extra space — no recursion and no stack or queue of your own. You are permitted to modify the tree's pointers while traversing, but the tree must be left exactly as you found it when the traversal ends. An empty tree returns an empty list.

CONSTRAINTS
  • The number of nodes in the tree is in the range [0, 10⁵]
  • -100 <= Node.val <= 100
  • O(1) auxiliary space — the output list does not count
  • The tree's structure must be fully restored before you return
EXAMPLE 1
Input: root = [1,null,2,3]
Output: [1,3,2]
1 has nothing on its left, so it is emitted first; within 2's subtree, 3 sits on the left and therefore precedes 2. Identical to the recursive in-order result — the space saving changes nothing about the output.
EXAMPLE 2
Input: root = [4,2,6,1,3,5,7]
Output: [1,2,3,4,5,6,7]
In-order on a search tree yields sorted values, which makes this a convenient input for checking a traversal implementation.
EXAMPLE 3
Input: root = a left-leaning chain of 100000 nodes
Output: the 100000 values, bottom to top
This is the case that motivates the technique: recursion would need 100000 live frames and an explicit stack would hold 100000 entries, while this approach holds none.
EXAMPLE 4
Input: root = []
Output: []
No nodes means nothing to emit, and the loop never runs.
Why is O(H) space not good enough?
Usually it is. This matters when the tree is huge and possibly chain-shaped, or when you are on a device with very tight memory — the height is not bounded by anything unless the tree is known to be balanced.
Am I allowed to modify the tree?
Temporarily, yes, but every change has to be undone before you return. That restoration requirement is what makes the algorithm subtle rather than merely clever.
Is it safe if another thread reads the tree at the same time?
No — mid-traversal the tree genuinely contains extra links. Worth flagging in an interview: the technique assumes exclusive access.
Does it still run in O(N) time?
Yes, though not obviously — some edges are walked more than once. The counting argument is that each edge is traversed at most three times, which keeps the total linear.

Every traversal so far has needed somewhere to remember the way back. Recursion used the call stack; the iterative version used a stack we allocated ourselves. Both cost O(H), and on a chain-shaped tree that is O(N). The question here is whether the remembering can be avoided altogether.

The obstacle is precise. In-order means: finish the left subtree, then emit this node, then do the right subtree. Once you descend into the left subtree, links only lead further downward — there is no way back up to the node you still owe an emission to. That single missing return route is the entire reason a stack exists.

The tree is full of unused pointers

Count them. A tree with N nodes has 2N pointer slots and only N - 1 of them point at anything, so N + 1 slots sit empty. That is more empty slots than nodes. If one of them could be made to point back where we need to return, the stack becomes unnecessary.

Which slot? Ask where we will be standing at the exact moment we need to come back to node v. We need to return to v right after its left subtree is finished — that is, immediately after the last node the in-order walk visits inside that subtree. In-order finishes a subtree at its rightmost node, so that node is v's in-order predecessor: go once into v.left, then follow right links as far as they go.

And now the fact that makes everything work: that rightmost node's right pointer is, by definition, empty — it is rightmost precisely because it has nothing to its right. So there is a free slot sitting exactly where we need one. Point it at v, and when the walk falls off the end of the left subtree it lands back on v automatically, with no memory required.

Borrow, then give back

A link we invent is not part of the tree, so it has to be removed before we finish — and removing it is also how we detect that we have already been down that way.

Standing on curr:

- No left child. Nothing is owed to us from the left, so emit curr and move right. (That right link may be a real edge or a borrowed one; either way it leads where we should go next.)
- A left child exists. Walk to the predecessor: one step left, then right as far as possible — stopping if a right pointer already leads back to curr, which would otherwise loop forever.
- Predecessor's right is empty. We have not entered this subtree yet. Borrow the slot — point it at curr — and step left. Do not emit curr: its left subtree comes first.
- Predecessor's right already points at curr. We are arriving back along a link we borrowed earlier, so the left subtree is finished. Return the slot to null, emit curr, and move right.

Each node with a left child is therefore reached twice — once going down, once coming back — and the state of the predecessor's pointer is what distinguishes the two visits. That is the same information a stack was storing, kept inside the tree instead of beside it.

python
out, curr = [], root
while curr:
    if curr.left is None:
        out.append(curr.val)             # nothing owed on the left
        curr = curr.right                # real edge or borrowed thread
    else:
        pred = curr.left                 # find the in-order predecessor
        while pred.right and pred.right is not curr:
            pred = pred.right
        if pred.right is None:
            pred.right = curr            # borrow: leave a way back
            curr = curr.left             # descend, emitting nothing yet
        else:
            pred.right = None            # give it back — the tree is restored here
            out.append(curr.val)         # left subtree done, so emit now
            curr = curr.right
Crucial Notethe inner search stops on pred.right is not curr as well as on null, and omitting that second test is the classic bug. Once a thread has been installed, following right pointers from curr.left will eventually walk along it straight back to curr and then loop forever. The two stopping conditions correspond exactly to the two situations we need to tell apart: a null means "first visit, install the thread", a pointer to curr means "second visit, remove it". Every borrowed pointer is cleared in that second branch, which is what leaves the tree in its original state — and it is worth checking that promise deliberately, since a half-finished traversal that exits early would leave the tree corrupted.
Worked example:root 1, right child 2, and 2's left child 3
- curr = 1. No left child, so emit 1 and move right to 2.
- curr = 2. It has a left child (3). Predecessor: step left to 3, then right as far as possible — 3 has no right, so 3 is the predecessor. Its right is null, so borrow it: 3.right = 2. Step left to 3. Nothing emitted yet.
- curr = 3. No left child, so emit 3 and move right — along the borrowed link, back up to 2.
- curr = 2. It still has a left child, so find the predecessor again: 3, whose right now points at 2. That is the "already been here" signal. Clear it (3.right = null, tree restored), emit 2, move right to null.
- The loop ends. Output [1, 3, 2], and the tree is exactly as it started.
What it costs, and where it fits

Extra space is O(1) — two pointers, regardless of the tree's size or shape. Time is still O(N), though it takes an argument: the predecessor searches walk some edges repeatedly, but each edge is walked at most three times in total (once descending, once during the predecessor search that installs a thread, once during the search that removes it), so the work stays proportional to the number of edges.

The price is real and worth stating: the code is longer and much easier to get wrong than a four-line recursion, and it temporarily mutates the tree, which rules it out for shared or read-only structures. Reach for it when the height is genuinely unbounded and memory is genuinely tight — otherwise the recursive or explicit-stack versions are better engineering.

The idea that transfers is bigger than this algorithm: unused capacity in a data structure can substitute for auxiliary memory. The same instinct produces threaded binary trees, the XOR-linked list, the O(1)-space rewiring in Flatten Binary Tree, and cycle-detection tricks that mark visited nodes in place. Whenever an algorithm needs to remember something, it is worth asking whether the structure itself has room to hold that memory — and, just as importantly, whether you can put it back.

Interactive Strategy Visualization
ABCDE
Traversal State
A
Focus

Find Successor

Start at 'A'. Find rightmost node in left subtree.

Strategy

Morris Traversal achieves O(1) Space by temporary 'threading'.

"Peak efficiency in tree navigation."

Strategy

Focus on the recursive nature of trees: solve for subtrees and combine results at the root.

"Divide and Conquer: Subproblem → Recurrence → Result"

O(N) Time · O(H) Space Recursion
O(N) Time · O(H) Space Explicit Stack
O(N) Time · O(1) Space Threaded Traversal