DAY 19 / 30 · WEEK 3

Trees — BFS & DFS

Goal of the day

Concept

A tree is a hierarchy: one root node at the top, connected by edges down to child nodes, each of which can have children of its own. Unlike Day 15's linked list — a straight chain, one .next per node — a tree branches. A binary tree puts one specific limit on that branching: every node has at most two children, conventionally called left and right.

Think of a company org chart deliberately kept flat: every manager (a node) has at most two direct reports (its left and right children) — no more. The CEO is the root; anyone with no direct reports is a leaf. A family tree drawn top-down works the same way once you cap each person at two "children" slots — same shape, different labels.

Picture exploring a multi-floor building maze-style, breadth-first: you fully explore every room on floor 1 before taking a single step onto floor 2, then fully explore floor 2 before floor 3. That's BFS (breadth-first search) — visit every node at the current depth before moving to the next depth, using a queue (first discovered, first explored) to enforce that order.

Now picture the opposite strategy: pick a corridor and follow it all the way to its dead end before backtracking to try the next one. That's DFS (depth-first search) — go as deep as possible down one branch before backtracking, using a stack (most recently discovered, next explored) or plain recursion (the call stack does the same job for free) to enforce that order.

Diagram: anatomy of a binary tree

Anatomy: a 7-node binary tree, 3 levels deep 1 2 3 4 5 6 7 root parent leaf depth 0 depth 1 depth 2 root = top node. parent → child = connected by an edge. leaf = a node with no children (the bottom of the tree).

Diagram: BFS order vs. DFS order on the same tree

BFS (queue) — visits level by level 1 2 3 4 5 6 7 Visit order: 1, 2, 3, 4, 5, 6, 7 — matches the labels. DFS (stack) — down one branch, then backtrack 1 2 3 4 5 6 7 Visit order: 1, 2, 4, 5, 3, 6, 7 — a different order!

Interactive visualization: BFS (level order)

waiting in the queue being dequeued & visited now already visited
Press Play or Step to begin.

Interactive visualization: DFS (preorder, iterative stack)

waiting on the stack being popped & visited now already visited
Press Play or Step to begin.

The code: BFS with a queue, DFS with a stack

class TreeNode {
  constructor(val, left = null, right = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

function bfs(root) {
  const result = [];
  if (!root) return result;          // empty tree — nothing to visit
  const queue = [root];
  while (queue.length) {
    const node = queue.shift();       // FIFO: take the OLDEST discovered node
    result.push(node.val);
    if (node.left) queue.push(node.left);
    if (node.right) queue.push(node.right);
  }
  return result;
}

function dfs(root) {
  const result = [];
  if (!root) return result;          // empty tree — nothing to visit
  const stack = [root];
  while (stack.length) {
    const node = stack.pop();         // LIFO: take the NEWEST discovered node
    result.push(node.val);
    if (node.right) stack.push(node.right); // push right first...
    if (node.left) stack.push(node.left);   // ...so left is popped (visited) first
  }
  return result;
}

Line by line:

Dry run: bfs(root) on the tree above

StepDequeueChildren enqueuedQueue afterVisited so far
112, 3[2, 3][1]
224, 5[3, 4, 5][1, 2]
336, 7[4, 5, 6, 7][1, 2, 3]
44— (leaf)[5, 6, 7][1, 2, 3, 4]
55— (leaf)[6, 7][1, 2, 3, 4, 5]
66— (leaf)[7][1, 2, 3, 4, 5, 6]
77— (leaf)[][1, 2, 3, 4, 5, 6, 7]

Dry run: dfs(root) on the same tree

StepPopChildren pushed (right, left)Stack after (bottom→top)Visited so far
113, 2[3, 2][1]
225, 4[3, 5, 4][1, 2]
34— (leaf)[3, 5][1, 2, 4]
45— (leaf)[3][1, 2, 4, 5]
537, 6[7, 6][1, 2, 4, 5, 3]
66— (leaf)[7][1, 2, 4, 5, 3, 6]
77— (leaf)[][1, 2, 4, 5, 3, 6, 7]

Same tree, same starting node — two entirely different orders. BFS finishes an entire level before starting the next; DFS drains the whole left branch (down to leaves 4 and 5) before it ever backtracks to node 3.

Complexity

TraversalTimeSpaceWhy
BFS (queue)O(n)O(w)Every node is visited exactly once (O(n) time). The queue's peak size is the widest level of the tree — w — since an entire level can sit in the queue at once.
DFS (stack or recursion)O(n)O(h)Every node is visited exactly once (O(n) time). The stack's (or call stack's) peak size is the tree's height — h — since it only ever holds one path from root to the current node.

n = total nodes, w = the widest level's node count, h = the tree's height. These are not interchangeable: on a balanced tree, w ≈ n/2 and h ≈ log n, so DFS uses far less space. On a wide-but-shallow tree (many nodes, few levels), w can approach n while h stays tiny — DFS wins big. On a narrow-but-skewed tree (essentially a linked list), h can approach n while w stays tiny — now BFS's queue barely grows, and DFS is the one paying O(n) space. The tree's shape decides which one is cheaper, not the algorithm name.

Interview corner

How to talk through it out loud: name the data structure before writing a single line — "BFS needs a queue because I want first-in-first-out, level-by-level order; DFS needs a stack, or recursion, because I want last-in-first-out, depth-first order." Then narrate the empty-tree guard out loud before touching .val — "first, what happens if root is null?" Interviewers watching a tree-traversal solution are listening for exactly these two things: do you reach for the right structure on purpose, not by memorized template, and do you handle the empty case before you ever dereference a node.

Practice problems

1. Invert Binary Tree (Easy — LeetCode: Invert Binary Tree)

Given the root of a binary tree, swap every node's left and right children, all the way down, and return the new root.

2. Maximum Depth of Binary Tree (Easy — LeetCode: Maximum Depth of Binary Tree)

Given the root of a binary tree, return its maximum depth — the number of nodes along the longest path from the root down to the farthest leaf.

3. Binary Tree Level Order Traversal (Medium — LeetCode: Binary Tree Level Order Traversal)

Given the root of a binary tree, return the values of its nodes grouped level by level (an array of arrays, one per depth) — today's BFS, applied directly.

Quiz

1. Which data structure does BFS use to control visiting order?

2. Which data structure does today's iterative DFS use?

3. On today's tree, why does BFS's visit order come out as exactly 1, 2, 3, 4, 5, 6, 7 — the same as the node labels?

4. On a complete binary tree with about 1,000 nodes (roughly 10 levels deep, so the widest level holds close to 500 nodes), which traversal's extra space blows up the most?

5. What should bfs(null) (or dfs(null)) return, and why does it matter?