Coding · Data structures
Binary tree traversal explained: preorder, inorder, postorder and level order
Short answer
Traversing a binary tree means visiting every node exactly once in a fixed order. The three depth-first orders differ only in when the node itself is visited: preorder visits node, left, right; inorder visits left, node, right; postorder visits left, right, node. Level-order traversal is breadth-first and visits the tree row by row using a queue. All four take O(n) time.
Why traversal order matters
A tree has no single natural order, so algorithms choose the one that fits the task. Inorder traversal of a binary search tree yields the keys in sorted order. Preorder visits a parent before its children, which is what you need to copy a tree or write it out so it can be rebuilt. Postorder visits children before the parent, which is what you need to delete a tree, compute folder sizes, or evaluate an expression tree. Level order finds the shallowest node first and prints a tree row by row.
The four orders on one example
We use this tree: root 1 with children 2 and 3; node 2 has children 4 and 5; node 3 has a right child 6.
1
/ \
2 3
/ \ \
4 5 6| Order | Rule | Result |
|---|---|---|
| Preorder | node, left, right | 1 2 4 5 3 6 |
| Inorder | left, node, right | 4 2 5 1 3 6 |
| Postorder | left, right, node | 4 5 2 6 3 1 |
| Level order | row by row, left to right | 1 2 3 4 5 6 |
A simple way to remember the first three: the name says where the node goes. Pre means before both subtrees, in means between them, post means after them. The left subtree is always visited before the right one.
Recursive implementations
The recursive versions mirror the definitions directly. Each function visits the node at a different line.
class Node:
def __init__(self, val, left=None, right=None):
self.val, self.left, self.right = val, left, right
def preorder(node, out):
if node:
out.append(node.val)
preorder(node.left, out)
preorder(node.right, out)
return out
def inorder(node, out):
if node:
inorder(node.left, out)
out.append(node.val)
inorder(node.right, out)
return out
def postorder(node, out):
if node:
postorder(node.left, out)
postorder(node.right, out)
out.append(node.val)
return out
root = Node(1, Node(2, Node(4), Node(5)), Node(3, None, Node(6)))
print(preorder(root, [])) # [1, 2, 4, 5, 3, 6]
print(inorder(root, [])) # [4, 2, 5, 1, 3, 6]
print(postorder(root, [])) # [4, 5, 2, 6, 3, 1]Iterative preorder and inorder with a stack
Recursion uses the call stack. To avoid recursion limits on deep trees, manage the stack yourself. For preorder, pop a node, visit it, then push the right child before the left child so that the left one is processed first.
For inorder, walk left as far as possible, pushing nodes on the way. When you cannot go further left, pop a node, visit it, and move to its right child.
def preorder_iter(root):
out, stack = [], [root] if root else []
while stack:
node = stack.pop()
out.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return out
def inorder_iter(root):
out, stack, node = [], [], root
while stack or node:
while node: # go as far left as possible
stack.append(node)
node = node.left
node = stack.pop()
out.append(node.val)
node = node.right
return out
print(preorder_iter(root)) # [1, 2, 4, 5, 3, 6]
print(inorder_iter(root)) # [4, 2, 5, 1, 3, 6]Iterative postorder and level order
Iterative postorder is the trickiest. One clean approach uses a flag: push each node twice, the second time marked as ready to visit after its children. Level order uses a queue instead of a stack; processing the queue one level at a time also tells you the depth of each node.
from collections import deque
def postorder_iter(root):
out, stack = [], [(root, False)] if root else []
while stack:
node, children_done = stack.pop()
if children_done:
out.append(node.val)
else:
stack.append((node, True))
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, False))
return out
def level_order(root):
levels, queue = [], deque([root] if root else [])
while queue:
level = []
for _ in range(len(queue)): # nodes of the current level
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
levels.append(level)
return levels
print(postorder_iter(root)) # [4, 5, 2, 6, 3, 1]
print(level_order(root)) # [[1], [2, 3], [4, 5, 6]]Another common trick for postorder: run a modified preorder (node, right, left) and reverse the result.
Time and space complexity
| Traversal | Time | Extra space | Reason |
|---|---|---|---|
| Preorder / inorder / postorder (recursive) | O(n) | O(h) | the call stack holds one frame per level |
| Preorder / inorder / postorder (iterative) | O(n) | O(h) | the explicit stack holds at most one path plus siblings |
| Level order | O(n) | O(w) | the queue holds at most one full level |
| Morris inorder traversal | O(n) | O(1) | temporarily rewires null right links into threads |
Here n is the number of nodes, h the height and w the maximum width. For a balanced tree h is about log2(n) and w can be about n/2; for a chain-shaped tree h is n and w is 1.
Common mistakes
- Pushing the left child before the right child in iterative preorder. A stack is LIFO, so the child you want first must be pushed last.
- Forgetting the empty tree: every function should return an empty result for
root is None. - Using a
listwithpop(0)as the queue in level order. It is O(n) per pop; usecollections.deque. - Relying on recursion for very deep trees. Python stops at its recursion limit (about 1000 by default) with
RecursionError. - Expecting a single traversal to identify a tree. Preorder alone is ambiguous; preorder plus inorder (with distinct values) is enough to rebuild it.
When to use which traversal
| Task | Traversal |
|---|---|
| List BST keys in sorted order, validate a BST, k-th smallest | Inorder |
| Copy or serialize a tree, print a directory outline | Preorder |
| Delete a tree, compute subtree sizes or heights, evaluate an expression tree | Postorder |
| Minimum depth, right-side view, print by level, nearest node | Level order |
Preorder, inorder and postorder are all forms of depth-first search; level order is breadth-first search applied to a tree. The same ideas extend to general graphs, where you also need a set of visited nodes.
FAQ
What is the difference between preorder, inorder and postorder?
Only the position of the node visit. Preorder visits the node before its subtrees, inorder between the left and right subtree, and postorder after both subtrees.
Which traversal gives sorted output for a binary search tree?
Inorder traversal. In a BST all left keys are smaller and all right keys are larger, so visiting left, node, right produces keys in ascending order.
Is level-order traversal the same as BFS?
Yes. Level-order traversal is breadth-first search on a tree. It uses a queue and visits nodes in order of their depth.
Why use an iterative traversal instead of recursion?
Recursion depth equals tree height, and Python raises RecursionError past its recursion limit. An explicit stack has no such limit and makes it easy to pause or stop early.
Can you traverse a tree with O(1) extra space?
Yes. Morris traversal temporarily links each node's in-order predecessor back to it, so no stack is needed, and it restores the tree before finishing. It runs in O(n) time.