Wiki Journal

A depth-first tree iterator

The iterator pattern is nice because it gives the caller a simple flat-looking looping mechanism to traverse data entries. Consider the following pseudocode for traversing a tree depth-first.

const tree = {
  id: 1,
  left: {
    id: 2,
    left: { id: 3, left: null, right: null }
    right: null,
  },
  right: {
    id: 4,
    left: {
      id: 5,
      left: null,
      right: { id: 6, left: null, right: null },
    },
    right: {
      id: 7,
      left: null,
      right: { id: 8, left: null, right: null },
    }
  }
};

traverse(tree, onEmit);

function onEmit(node) {
  console.log(node.id);
}

function traverse(node, onEmit) {
  if (node == null) return;
  
  onEmit(node);
  
  if (node.left) traverse(node.left, onEmit);
  if (node.right) traverse(node.right, onEmit);
}

The program above takes a recursive approach to traversing a tree structure. While this may not be ideal in situations with very large tree structures, the program would print the following output when run:

1
2
3
4
5
6
7
8

Consider instead a breadth-first approach to traversing a tree:


function traverse(node, onEmit) {
  if (node == null) return;
  
  emit(node, onEmit);
  descend(node, onEmit);
}

function emit(node, onEmit) {
  if (node) onEmit(node);
  if (node.left) onEmit(node.left);
  if (node.right) onEmit(node.right);
}

function descend(node, onEmit) {
  traverse(node.left, onEmit);
  traverse(node.right, onEmit);
}

Note that I actually think the logic above is wrong.

---

Let's take the depth-first example and turn it into an iterator.

An iterator is an object with a next method that will return the following node in the tree. Once there are no more nodes, the next method will return null.

Let's look at the depth-first example again.

function onEmit(node) {
  console.log(node.id);
}

function traverse(node, onEmit) {
  if (node == null) return;
  
  onEmit(node);
  
  if (node.left) traverse(node.left, onEmit);
  if (node.right) traverse(node.right, onEmit);
}

Let's try to first model the recursion in an iterative manner.

function traverse(node, onEmit) {
  if (node == null) return;
  
  onEmit(node);
  
  // This should be represented by some data structure
  if (node.left) traverse(node.left, onEmit);
  // This should be represented by some data structure
  if (node.right) traverse(node.right, onEmit);
}

Let's think of the steps that are executed and see how we can decompose this recursive function into an iterative one.

Here's our example tree again:

const tree = {
  id: 1,
  left: {
    id: 2,
    left: { id: 3, left: null, right: null }
    right: null,
  },
  right: {
    id: 4,
    left: {
      id: 5,
      left: null,
      right: { id: 6, left: null, right: null },
    },
    right: {
      id: 7,
      left: null,
      right: { id: 8, left: null, right: null },
    }
  }
};

** At this point, we realize that we're checking if node.left and node.right are null before we traverse. Then, we check again at the start of traverse. Let's just do it at the start of traverse only.

function traverse(node, onEmit) {
  if (node == null) return;
  
  onEmit(node);
  
  traverse(node.left, onEmit);
  traverse(node.right, onEmit);
}

Let's start again.

---

I think we need to come up with a max

Last built on 2025-11-11