Skip to content
beginner

Binary Tree Inorder Traversal

The difficult part of inorder traversal is not remembering “left, node, right.” It is preserving the parent node while the left subtree is still…

Published 2026-09-07Updated 2026-09-1210 min read
Smartphone displaying AI app with book on AI technology in background.
Smartphone displaying AI app with book on AI technology in background. Photo by Sanket Mishra on Pexels.
Problem

Binary Tree Inorder Traversal

Difficulty: EasyAcceptance rate: 80.6%

Given the root of a binary tree, return the values of its nodes in inorder: traverse the left subtree, then the node, then the right subtree.

StackTreeDepth-First SearchBinary Tree

Constraints

  • The number of nodes in the tree is in the range [0, 100].
  • -100 <= Node.val <= 100

Important details

  • The input may be an empty tree, in which case return an empty list.
  • The output contains node values in left-root-right order.
  • The source permits either a recursive or iterative implementation; the follow-up asks whether it can be done iteratively.

The difficult part of inorder traversal is not remembering “left, node, right.” It is preserving the parent node while the left subtree is still unfinished.

The clean solution direction is:

  • Recursion stores waiting ancestors in the language call stack.
  • Iteration stores those same ancestors in an explicit stack.

Once you see traversal as managing unfinished work, both implementations become the same algorithm with different storage.

Read the traversal contract

The function receives the root of a binary tree and returns a list of node values in inorder:

  1. Traverse the entire left subtree.
  2. Visit the current node.
  3. Traverse the entire right subtree.

That rule applies recursively at every node.

For this tree:

      1
     / \
    2   3
     \ /
      4 5

The traversal is:

[2, 4, 1, 5, 3]

Why?

  • The left subtree of 1 produces [2, 4].
  • Then we visit 1.
  • The right subtree produces [5, 3].

An empty tree has no nodes to visit, so it returns:

[]

One important boundary: inorder output is sorted only when the input is a binary search tree with the required ordering property. An arbitrary binary tree has a defined inorder sequence, but that sequence does not have to be sorted.

Spot the depth-first search signal

This problem gives you the traversal order directly:

left work → current node → right work

That is a depth-first search pattern because each subtree must be completed before control returns to its parent.

The parent node has a specific obligation:

Wait until the left subtree finishes, emit the parent value, then begin the right subtree.

That waiting point is the whole problem.

Compare the three common visit positions:

  • Preorder: visit node, then left, then right
  • Inorder: left, visit node, then right
  • Postorder: left, right, then visit node

Moving the visit operation changes the answer. If you append a value before processing the left child, you have written preorder. If you append it after processing the right child, you have written postorder.

For inorder, the recurrence is:

inorder(node) =
    inorder(node.left)
    visit(node)
    inorder(node.right)

I would start with the recursive version in an interview because it mirrors the contract exactly. Then I would derive the iterative version by asking: what state does recursion remember for us?

Derive the recursive solution

Assume the tree node has the usual interface:

node.val
node.left
node.right

The helper has three jobs:

  1. Stop when it receives None.
  2. Traverse the left child.
  3. Append the current value.
  4. Traverse the right child.

The result list is shared output state. The helper does not need to return a new list from every recursive call; it can append values to the one list owned by the outer function.

class Solution:
    def inorderTraversal(self, root):
        result = []

        def dfs(node):
            if node is None:
                return

            dfs(node.left)
            result.append(node.val)
            dfs(node.right)

        dfs(root)
        return result

The call stack supplies the hidden state. When dfs(node) calls dfs(node.left), the current node is not lost. The runtime keeps the parent call suspended, including the fact that it should append the parent value after the left call returns.

That return point matters:

dfs(node.left)
result.append(node.val)

When the left recursive call finishes, execution resumes at result.append(node.val). The parent is now eligible to be visited.

A common incorrect version looks like this:

result.append(node.val)
dfs(node.left)
dfs(node.right)

That emits the node before either subtree, which is preorder. The code can still look like a valid DFS, but it violates the specific contract.

Trace the recursive state

Use the example:

      1
     / \
    2   3
     \ /
      4 5

The recursive execution looks like this:

Current nodeActionOutput
1Descend left[]
2No left child; visit 2[2]
2Descend right to 4[2]
4Visit 4[2, 4]
1Left call returned; visit 1[2, 4, 1]
3Descend left to 5[2, 4, 1]
5Visit 5[2, 4, 1, 5]
3Left call returned; visit 3[2, 4, 1, 5, 3]

Notice what happens at node 1. The function reaches 1, pauses it, explores 2, explores 4, returns from those calls, and only then appends 1.

That is the hidden control flow beginners often lose when they try to write the iterative version.

For an empty tree, the trace is shorter:

dfs(None)
→ return
→ result is []

For a one-sided tree, the same rule still works. A right-only chain such as 1 → 2 → 3 produces [1, 2, 3]. A left-only chain produces [3, 2, 1]. No special case is needed because None is the base case for every missing child.

Turn recursion into an explicit stack

A binary tree with nodes 1, 2, 4, 3, and 5 is traced left to right through stack states: push 1 and 2, pop and visit 2, push and visit 4, pop and visit 1, then process 3 and 5, producing the order 2, 4, 1, 5, 3.
The explicit stack preserves the same waiting ancestors that recursion stores implicitly, ensuring each node is visited after its left subtree and before its right subtree.

To remove recursion, identify what the call stack was storing.

While descending left, each ancestor is waiting:

  • Its left subtree is being explored.
  • Its own value has not been emitted yet.
  • After it is emitted, its right subtree must be explored.

The explicit stack stores exactly those waiting ancestors.

The iterative algorithm has two phases:

  1. While current is a real node, push it and move left.
  2. When current is None, pop the next waiting node, visit it, and move to its right child.
class Solution:
    def inorderTraversal(self, root):
        result = []
        stack = []
        current = root

        while current is not None or stack:
            while current is not None:
                stack.append(current)
                current = current.left

            current = stack.pop()
            result.append(current.val)
            current = current.right

        return result

Each variable answers a specific obligation:

  • current points to the next subtree we should descend into.
  • stack holds ancestors whose left work is complete or in progress, but whose values are waiting to be visited.
  • result records values in the required order.

The outer loop must continue while either piece of work remains:

while current is not None or stack:

Checking only current is not None would stop too early. After reaching the leftmost leaf, current becomes None, but the stack still contains ancestors waiting to be popped.

Iterative stack trace

For the same tree, the important transitions are:

Stepcurrent actionStack after actionOutput
1Push 1, move left[1][]
2Push 2, move left[1, 2][]
32.left is None; pop 2, move right[1][2]
4Push 4, move left[1, 4][2]
5Pop 4, move right to None[1][2, 4]
6Pop 1, move right to 3[][2, 4, 1]
7Push 3, then 5[3, 5][2, 4, 1]
8Pop 5, move right to None[3][2, 4, 1, 5]
9Pop 3[][2, 4, 1, 5, 3]

At every pop, the node is ready to visit because its left subtree has already been handled. Its right subtree has not been handled yet, so moving current to node.right preserves the contract.

Iterative invariant: The stack contains waiting ancestors in an order such that the top node is the next node whose left work is complete and whose value can be visited.

The stack is not a generic bag of nodes. It is a record of unfinished obligations.

Prove both versions correct

Recursive version

Use structural induction on the subtree.

  • Base case: For node is None, there are no values to return, so the helper does nothing. That is correct.
  • Inductive step: Assume the helper correctly traverses both child subtrees. The helper processes the left subtree first, appends the current node, and processes the right subtree second. Therefore it produces exactly left-node-right order for the current subtree.

Since the root call applies this rule to the whole tree, the result is correct.

Iterative version

Maintain the stack invariant described above.

When the algorithm pushes a node, it is moving left before visiting that node. Therefore the node cannot be popped until the traversal has finished descending through its left side.

When the algorithm pops a node:

  1. Its left subtree has already been processed.
  2. Its value is appended next.
  3. current moves to its right subtree, which is processed afterward.

So every pop respects left-node-right order.

Each node is pushed once and popped once. A node is appended exactly when it is popped, so no node is omitted or visited twice.

Complexity, edge cases, and checks

Let n be the number of nodes and h the tree height.

Time

Both versions run in:

O(n)

The recursive version executes one helper call per node, plus calls for missing children. The iterative version pushes and pops each real node once. There is no repeated subtree traversal.

Space

The output list requires:

O(n)

That is required to return all node values.

The auxiliary traversal space is:

O(h)
  • Recursion uses the call stack.
  • Iteration uses the explicit stack.

A balanced tree has smaller maximum depth than a one-sided chain with the same number of nodes. The shape of the tree controls how many ancestors can be waiting at once.

The iterative version makes that memory visible and avoids relying on recursive calls. The recursive version is shorter and usually easier to explain. My practical rule is simple: use recursion when the contract-to-code mapping is the clearest answer; use the explicit stack when the interviewer asks for iteration or when you want direct control over traversal state.

Edge-case checks

Run these cases before trusting the implementation:

Tree shapeExpected behavior
Empty treeReturn []
One nodeReturn [value]
Left-only chainValues appear from deepest left node back toward the root
Right-only chainValues appear from root toward the deepest right node
Both childrenLeft values, root value, then right values
Repeated valuesPreserve every occurrence; do not deduplicate
Any shapeReturn values, not node objects, and do not modify links

These tests target the actual failure points: missing base cases, incorrect visit timing, and losing pending ancestors.

The transferable pattern

When the required order is:

left work → node work → right work

ask what must wait between the two subtree operations.

Recursion stores that waiting parent implicitly in the call stack. Iteration stores it visibly in a stack while a pointer walks left.

That is the reusable recognition rule. Do not memorize the loop as a trick. Track the unfinished obligation: every node is visited after its left work and before its right work. Test the empty tree and one-sided trees first, then verify that the state still obeys that rule at every step.

References

  1. leetcode/solution/0000-0099/0094.Binary Tree Inorder ...github.com
  2. LeetCode 94 Binary Tree Inorder Traversal Solution & Explanation | NeetCodeneetcode.io
7sources checked
7source domains
5searches run

Research updated Sep 7, 2026

Related sites

Strengthen the language foundations behind the solution

Use LearnPyFast and LearnJSFast when you want to reinforce the language mechanics that support interview implementations.

Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast
JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast

Keep grinding

Related coding interview problems

Continue with nearby problems that reuse the same data-structure, invariant, or optimization pattern.

Detailed view of a sewing machine needle stitching fabric, highlighting sewing process and textile technology.
advanced
12 min read

Recover Binary Search Tree

A binary search tree has a structural invariant that is more useful than its parent-child relationships: inorder traversal visits values in sorted order.…

View solution
Bright geometric pattern on a fence with green and white circles against a blue sky.
beginner
10 min read

Same Tree

A tree comparison fails the moment you forget that a missing child is also part of the structure.

View solution