Trees

Learn the vocabulary once, then the four traversals — and see why each one is the right answer to a different question.

14of 24
≈ 12Min read
5Parts

The words

The height is what every complexity in the next three lessons is expressed in. A balanced tree of n nodes has height log n; a degenerate one has height n and is a linked list wearing a costume. Almost all of tree engineering is about keeping that from happening.

You already use trees constantly: the DOM, a filesystem, JSON, an abstract syntax tree, a database index, the decision tree in a routing table.

TermMeans
RootThe one node with no parent
LeafA node with no children
HeightThe longest path from the root down to a leaf, counted in edges
DepthHow far one node is from the root
Binary treeAt most two children per node
CompleteEvery level full except possibly the last, which fills left to right
BalancedThe two subtrees of every node differ in height by at most one

The three depth-first orders

"Deleting a folder" is post-order and "printing a folder listing" is pre-order, and if you ever get them the wrong way round the operating system will tell you.

js
/* pre-order: node, left, right  — the node BEFORE its children */
function preOrder(node, out = []) {
  if (!node) return out;
  out.push(node.value);
  preOrder(node.left, out);
  preOrder(node.right, out);
  return out;
}

/* in-order: left, node, right   — the node BETWEEN its subtrees */
/* post-order: left, right, node — the node AFTER its children */
OrderUse it for
PreCopying or serialising a tree — you need the node before you can place its children
InA binary search tree: it comes out sorted
PostDeleting, freeing, or evaluating an expression — you need the children’s results first

Breadth first, level by level

The only traversal that is not naturally recursive, because it needs a queue rather than a stack. That single swap — queue instead of stack — is the entire difference between breadth-first and depth-first, and it comes back in the graph lessons unchanged.

Level order is what you want for "the shallowest node that…", for printing a tree by rows, and for anything where nearer means better.

js
function levelOrder(root) {
  const out = [];
  const queue = [root];

  for (let head = 0; head < queue.length; head++) {
    const node = queue[head];
    if (!node) continue;
    out.push(node.value);
    queue.push(node.left, node.right);
  }

  return out;
}

Depth, and the shape it implies

Notice how short these are. A tree is a recursive shape, so recursion over it is usually one line plus a null check — which is exactly the case where recursion is the right tool rather than the clever one.

That isBalanced is O(n²) because it recomputes the height at every node. Returning the height and the verdict together makes it O(n), and doing so is a good exercise.

js
const height = (node) => (node ? 1 + Math.max(height(node.left), height(node.right)) : 0);

const size = (node) => (node ? 1 + size(node.left) + size(node.right) : 0);

const isBalanced = (node) => {
  if (!node) return true;
  return Math.abs(height(node.left) - height(node.right)) <= 1
    && isBalanced(node.left) && isBalanced(node.right);
};

Traversing without recursion

Push right before left, because a stack reverses. This version survives a tree deep enough to overflow the call stack — which a degenerate tree of a million nodes certainly is.

the same pre-order, with the stack made explicit
function preOrderIterative(root) {
  const out = [];
  const stack = [root];

  while (stack.length) {
    const node = stack.pop();
    if (!node) continue;
    out.push(node.value);
    stack.push(node.right, node.left);     // right first, so left comes off first
  }

  return out;
}

Practice