Tree Traversals
Visit every node of a tree in the right order: preorder, inorder, postorder and level by level. Traced by hand, recursive and iterative, in JS and Python.
On this page
A company stores its org chart as a tree. Three people ask you for three different things. HR wants the chart printed as an indented list, each manager above their team. Finance wants the total salary of every team, which means you need each sub-team's total before you can add up the team above it. The CEO wants a list of who reports at each level: herself, then her direct reports, then theirs.
Same tree, three different orders of visiting the people in it. Visiting every node in a fixed order is called a traversal. In many tree problems, picking the right order is most of the work.
Depth first: three moments to visit a node
In Binary Trees you saw the pattern: handle the empty tree, recurse into the left child, recurse into the right child. That walk goes deep down one branch before it comes back, so it is called depth-first. It touches every node at three moments: on the way down, between its two children, and on the way back up.
"Visiting" a node means doing your real work there: printing it, adding it to a list, comparing it. The only question is at which of the three moments you do it.
- Preorder: visit the node, then its left subtree, then its right subtree. Parents come before their children. That is the HR outline.
- Inorder: left subtree, then the node, then the right subtree.
- Postorder: left subtree, then right subtree, then the node. Children come before their parents. That is the Finance totals.
Inorder, traced
Here is inorder on a six-node tree. The "calls" row is the call stack: every call that has started and not yet finished. A node cannot be visited until its whole left subtree is done.
inorder(node): if node is empty: return inorder(node.left) visit(node) inorder(node.right)- calls08output
1Start at 8. It cannot be visited yet: its whole left subtree comes first. Go left.
- calls0813output
2At 3, the same rule applies. Go left again.
- calls081321output01
3At 1, the left child is empty, so that call returns at once. Visit 1.
- calls0813output0113
41 has no right child, so its call finishes. Back at 3, the left subtree is done: visit 3.
- calls081326output011326
5Now 3's right subtree. 6 has an empty left child, so visit 6 straight away.
- calls08output01132638
6The calls for 6 and 3 finish. Back at 8, its whole left subtree is done: visit 8.
- calls08110output01132638410
7Go right to 10. Its left child is empty, so visit 10.
- calls08110214output01132638410514
8Go right to 14, a leaf. Visit it.
- callsoutput01132638410514
- visits
- 6
- deepest stack
- 3
9All calls finish. 6 nodes visited once each, and the stack was never deeper than the height, 3. The output is sorted, because every left subtree here holds smaller values.
That sorted output is no accident. This tree is a binary search tree, where everything left of a node is smaller and everything right is bigger. On any other tree, inorder just gives "left side, me, right side". For the same tree, the three depth-first orders are:
| Order | Result | Use it when |
|---|---|---|
| Preorder | 8, 3, 1, 6, 10, 14 | a parent must be handled before its children: printing an outline, copying or saving a tree |
| Inorder | 1, 3, 6, 8, 10, 14 | you want a search tree's values in sorted order |
| Postorder | 1, 6, 3, 14, 10, 8 | a node needs its children's answers first: height, subtree sums, deleting a tree |
The code
The three functions are identical except for where one line sits.
function preorder(node, out = []) {
if (node === null) return out;
out.push(node.val); // visit, then the children
preorder(node.left, out);
preorder(node.right, out);
return out;
}
function inorder(node, out = []) {
if (node === null) return out;
inorder(node.left, out);
out.push(node.val); // visit between the children
inorder(node.right, out);
return out;
}
function postorder(node, out = []) {
if (node === null) return out;
postorder(node.left, out);
postorder(node.right, out);
out.push(node.val); // visit after the children
return out;
}def preorder(node, out=None):
if out is None:
out = []
if node is not None:
out.append(node.val) # visit, then the children
preorder(node.left, out)
preorder(node.right, out)
return out
def inorder(node, out=None):
if out is None:
out = []
if node is not None:
inorder(node.left, out)
out.append(node.val) # visit between the children
inorder(node.right, out)
return out
def postorder(node, out=None):
if out is None:
out = []
if node is not None:
postorder(node.left, out)
postorder(node.right, out)
out.append(node.val) # visit after the children
return outWithout recursion
Recursion uses the call stack for you. On a very deep tree that stack can overflow, so you keep your own stack instead. For preorder it is short: pop a node, visit it, push its children. Push the right child first, so the left one is on top and comes out first.
function preorderIterative(root) {
const out = [];
const stack = root === null ? [] : [root];
while (stack.length > 0) {
const node = stack.pop();
out.push(node.val);
// push right first so that left is popped first
if (node.right !== null) stack.push(node.right);
if (node.left !== null) stack.push(node.left);
}
return out;
}def preorder_iterative(root):
out = []
stack = [] if root is None else [root]
while stack:
node = stack.pop()
out.append(node.val)
# push right first so that left is popped first
if node.right is not None:
stack.append(node.right)
if node.left is not None:
stack.append(node.left)
return outBreadth first: level by level
The CEO's request does not fit any depth-first order. She wants everyone at depth 0, then everyone at depth 1, and so on. This is level-order traversal, also called breadth-first.
The idea: keep a list of the nodes on the current level. Record their values. Then build the next level from their children, left to right. Repeat until a level comes out empty.
level = [root]while level is not empty: record the values in level next = children of level, left to right level = next- level08next
- result
- []
1The first level is just the root.
- level08next
- result
- [8]
2Record level 0: [8].
- level08next03110
- result
- [8]
3Collect the children of 8, left first: 3 and 10.
- level03110next
- result
- [8], [3, 10]
4They become the current level. Record it.
- level03110next0116214
- result
- [8], [3, 10]
5Children of 3 are 1 and 6. 10 has only a right child, 14. Order is kept: everything under 3 comes before everything under 10.
- level0116214next
- result
- [8], [3, 10], [1, 6, 14]
6Record level 2.
- levelnext
- result
- [8], [3, 10], [1, 6, 14]
- levels
- 3
7All three are leaves, so the next level is empty and the loop stops. Each node was recorded once and collected once.
function levelOrder(root) {
const levels = [];
let level = root === null ? [] : [root];
while (level.length > 0) {
levels.push(level.map((node) => node.val));
const next = [];
for (const node of level) {
if (node.left !== null) next.push(node.left);
if (node.right !== null) next.push(node.right);
}
level = next;
}
return levels;
}
// [[8], [3, 10], [1, 6, 14]]def level_order(root):
levels = []
level = [] if root is None else [root]
while level:
levels.append([node.val for node in level])
next_level = []
for node in level:
if node.left is not None:
next_level.append(node.left)
if node.right is not None:
next_level.append(node.right)
level = next_level
return levels
# [[8], [3, 10], [1, 6, 14]]This is breadth-first search on a tree. The usual way to write it is with a single queue: take from the front, add children at the back. Swapping two lists does the same thing and keeps the levels apart for free. Breadth-First Search uses the queue form on graphs.
Cost
| Approach | Time | Space | Why |
|---|---|---|---|
| Preorder, inorder, postorder | O(n) | O(h) | Each node is visited once. The stack holds one node per level of the current path. |
| Level order | O(n) | O(w) | w is the widest level. In a full tree the bottom level holds about half the nodes, so this can be O(n). |
Both kinds touch each node a constant number of times. They differ in memory: depth-first remembers one path, breadth-first remembers one whole level. On a wide, shallow tree, depth-first is lighter. On a long, thin one, breadth-first is.
Common mistakes
Check yourself
A tree has root 5, left child 2 and right child 7. 2 has a left child 1. What does postorder return?
You need the total size of every folder, where a folder's size is its own files plus all its subfolders. Which order fits?
A full binary tree has 1,023 nodes on 10 levels. Level order keeps a whole level in memory. How many nodes are on the widest level?
What to practise next
The sorted inorder output hinted at something bigger. Binary Search Trees arrange values so you can find, add and remove one without visiting the whole tree.