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.

16 minLesson 2 of 6
On this page
  1. Depth first: three moments to visit a node
  2. Inorder, traced
  3. The code
  4. Breadth first: level by level
  5. Cost
  6. Common mistakes
  7. Check yourself
  8. What to practise next

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.
B A C pre: B A C in: A B C post: A C B
Walk around the tree, hugging it on the left. You pass each node on its left (preorder), underneath (inorder) and on its right (postorder).

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 traversalUse the arrows or ← → keys
inorder(node):  if node is empty: return  inorder(node.left)  visit(node)  inorder(node.right)
  1. calls
    08
    output
    83101614

    1Start at 8. It cannot be visited yet: its whole left subtree comes first. Go left.

  2. calls
    08
    13
    output
    83101614

    2At 3, the same rule applies. Go left again.

  3. calls
    08
    13
    21
    output
    01
    83101614

    3At 1, the left child is empty, so that call returns at once. Visit 1.

  4. calls
    08
    13
    output
    01
    13
    83101614

    41 has no right child, so its call finishes. Back at 3, the left subtree is done: visit 3.

  5. calls
    08
    13
    26
    output
    01
    13
    26
    83101614

    5Now 3's right subtree. 6 has an empty left child, so visit 6 straight away.

  6. calls
    08
    output
    01
    13
    26
    38
    83101614

    6The calls for 6 and 3 finish. Back at 8, its whole left subtree is done: visit 8.

  7. calls
    08
    110
    output
    01
    13
    26
    38
    410
    83101614

    7Go right to 10. Its left child is empty, so visit 10.

  8. calls
    08
    110
    214
    output
    01
    13
    26
    38
    410
    514
    83101614

    8Go right to 14, a leaf. Visit it.

  9. calls
    output
    01
    13
    26
    38
    410
    514
    83101614
    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.

1 / 9

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:

OrderResultUse it when
Preorder8, 3, 1, 6, 10, 14a parent must be handled before its children: printing an outline, copying or saving a tree
Inorder1, 3, 6, 8, 10, 14you want a search tree's values in sorted order
Postorder1, 6, 3, 14, 10, 8a 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 out

Without 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 out

Breadth 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-order traversalUse the arrows or ← → keys
level = [root]while level is not empty:  record the values in level  next = children of level, left to right  level = next
  1. level
    08
    next
    83101614
    result
    []

    1The first level is just the root.

  2. level
    08
    next
    83101614
    result
    [8]

    2Record level 0: [8].

  3. level
    08
    next
    03
    110
    83101614
    result
    [8]

    3Collect the children of 8, left first: 3 and 10.

  4. level
    03
    110
    next
    83101614
    result
    [8], [3, 10]

    4They become the current level. Record it.

  5. level
    03
    110
    next
    01
    16
    214
    83101614
    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.

  6. level
    01
    16
    214
    next
    83101614
    result
    [8], [3, 10], [1, 6, 14]

    6Record level 2.

  7. level
    next
    83101614
    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.

1 / 7
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

ApproachTimeSpaceWhy
Preorder, inorder, postorderO(n)O(h)Each node is visited once. The stack holds one node per level of the current path.
Level orderO(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

Quick check

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.