Skip to content

Intermediate: core algorithms · Trees and binary search trees · topic 32 of 61 · 35 min

Traversals: DFS and BFS

Explanation

A traversal visits every node once in a fixed order. Depth-first search (DFS) goes down one branch before the next and comes in three orders: preorder (node, left, right), inorder (left, node, right) and postorder (left, right, node). Breadth-first search (BFS), also called level order, visits the tree level by level using a queue. All of them are O(n) time.

Why the order matters

A list has one natural order; a tree does not. Different jobs need different orders. To copy a tree you must create a parent before its children. To delete a tree, or to add up folder sizes, you must finish the children before the parent. To print a BST in sorted order you visit the left side, then the node, then the right side. To find the nearest node to the root that matches something, you go level by level. Knowing which order fits which job is a big part of tree interviews.

The three depth-first orders

Depth-first search (DFS) goes as deep as possible down one branch, then backs up and tries the next branch. The three orders differ only in WHEN you record the current node compared with its two subtrees.

OrderVisitResult for build([1, 2, 3, 4, 5, None, 6])Typical use
Preordernode, left, right1, 2, 4, 5, 3, 6Copy or serialise a tree; print a folder listing
Inorderleft, node, right4, 2, 5, 1, 3, 6Sorted output of a BST (Topic 4)
Postorderleft, right, node4, 5, 2, 6, 3, 1Delete a tree; compute sizes or heights from children up
Level orderlevel by level, left to right1, 2, 3, 4, 5, 6Shortest number of steps; print by level
python
def preorder(node, out):
    if node is None:
        return
    out.append(node.val)       # node first
    preorder(node.left, out)
    preorder(node.right, out)

def inorder(node, out):
    if node is None:
        return
    inorder(node.left, out)
    out.append(node.val)       # node in the middle
    inorder(node.right, out)

def postorder(node, out):
    if node is None:
        return
    postorder(node.left, out)
    postorder(node.right, out)
    out.append(node.val)       # node last

root = build([1, 2, 3, 4, 5, None, 6])
for walk in (preorder, inorder, postorder):
    out = []
    walk(root, out)
    print(walk.__name__, out)
# Output:
# preorder [1, 2, 4, 5, 3, 6]
# inorder [4, 2, 5, 1, 3, 6]
# postorder [4, 5, 2, 6, 3, 1]
Passing one list down avoids building and joining many small lists.

Iterative DFS with a stack

Recursion uses the call stack for you. Python stops at about 1,000 nested calls by default (RecursionError), so a skewed tree with 5,000 nodes breaks a recursive walk. Interviewers also like to ask for the iterative version. The idea is to keep your own stack (a Python list with append and pop).

Iterative inorder: go left as far as possible, pushing every node on the way. When you cannot go left, pop a node, record it, and move to its right child. Repeat while there is a current node or the stack is not empty.

python
def inorder_iterative(root):
    result, stack, node = [], [], root
    while node or stack:
        while node:              # go left as far as possible
            stack.append(node)
            node = node.left
        node = stack.pop()       # the leftmost unvisited node
        result.append(node.val)
        node = node.right        # then its right subtree
    return result

print(inorder_iterative(build([1, 2, 3, 4, 5, None, 6])))   # Output: [4, 2, 5, 1, 3, 6]

Iterative preorder is simpler: push the root; repeatedly pop a node, record it, then push its RIGHT child before its LEFT child, so the left one comes off the stack first. For postorder, one easy trick is to do a "node, right, left" walk the same way and reverse the result at the end, because the reverse of node-right-left is left-right-node.

Level order (BFS) with a queue

Breadth-first search (BFS) visits all nodes at depth 0, then depth 1, then depth 2. A queue (first in, first out) gives exactly that order: take a node from the front, then add its children at the back. Use collections.deque, because popleft() is O(1), while list.pop(0) is O(n).

To return the answer level by level, read the queue length at the start of each round. Exactly that many nodes belong to the current level; their children form the next level.

python
from collections import deque

def level_order(root):
    if root is None:
        return []
    levels = []
    queue = deque([root])
    while queue:
        level = []
        for _ in range(len(queue)):     # only the nodes of this level
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        levels.append(level)
    return levels

print(level_order(build([1, 2, 3, 4, 5, None, 6])))   # Output: [[1], [2, 3], [4, 5, 6]]

Zigzag level order is a common twist: left to right on level 0, right to left on level 1, and so on. Do a normal level order and reverse every second level (or add to the front of a deque on odd levels).

Time and space

Every traversal visits each node once and does O(1) work there, so all are O(n) time. Space differs. DFS keeps one path on the stack: O(h), which is O(log n) for a balanced tree and O(n) for a skewed one. BFS keeps one level in the queue: O(w), where w is the widest level; the last level of a perfect tree holds about n/2 nodes, so BFS can need O(n) space.

Common mistakes

  • Using list.pop(0) as a queue. Each call shifts the whole list, making BFS O(n²). Fix: collections.deque and popleft().
  • Reading len(queue) inside the loop condition while adding children, so levels mix. Fix: save the level size before the inner loop, as range(len(queue)) does.
  • Pushing left before right in iterative preorder, which visits the right side first. Fix: push right, then left.
  • Returning a new list from every recursive call and joining them (left + [val] + right). It works but copies lists again and again; for big trees pass one list down.
  • Recursing on a very deep tree in Python. Fix: use the iterative version, or say that sys.setrecursionlimit only moves the limit and can still crash.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Traversals: DFS and BFS · 1/8

Traversals

This lesson covers the four ways to walk a tree: preorder, inorder, postorder and level order.

Prefer a video by a teacher? Find free videos on “binary tree traversal preorder inorder postorder level order iterative stack queue” on YouTube ↗

In real life: Category menus on a shopping site

An e-commerce site keeps its categories as a tree: All → Electronics → Mobiles → Smartphones, All → Fashion → Men → Shirts, and so on. The top menu bar shows level 1, a hover menu shows level 2: that is level order. A breadcrumb trail such as "Electronics > Mobiles > Smartphones" is the path from the root found by DFS. A sitemap page that lists every category under its parent is a preorder walk.

python
from collections import deque

menu = {"All": ["Electronics", "Fashion"], "Electronics": ["Mobiles", "Laptops"],
        "Fashion": ["Men", "Women"], "Mobiles": [], "Laptops": [], "Men": [], "Women": []}
queue, level = deque(["All"]), 0
while queue:
    names = [queue.popleft() for _ in range(len(queue))]
    print(level, names)
    for name in names:
        queue.extend(menu[name])
    level += 1
Shows level 0 ['All'], level 1 ['Electronics', 'Fashion'], level 2 ['Mobiles', 'Laptops', 'Men', 'Women'].

Practice (0/4 done)

Task 1 · Code · Python

Write preorder(root) that returns a list of the values in preorder (node, then left subtree, then right subtree). An empty tree gives []. Example: preorder(build([1, 2, 3, 4, 5, None, 6])) returns [1, 2, 4, 5, 3, 6].

Task 2 · Code · Python

Write inorder_iterative(root) that returns the inorder list (left subtree, node, right subtree) WITHOUT recursion: use your own stack. The tests include a left-skewed tree with 5,000 nodes, which breaks a recursive version in Python. Example: inorder_iterative(build([1, 2, 3, 4, 5, None, 6])) returns [4, 2, 5, 1, 3, 6].

Task 3 · Code · Python

Write level_order(root) that returns a list of levels; each level is a list of values from left to right. An empty tree gives []. Use collections.deque as the queue (deque is already imported above). Example: level_order(build([3, 9, 20, None, None, 15, 7])) returns [[3], [9, 20], [15, 7]].

Task 4 · Code · Python

Write zigzag_level_order(root): like level order, but level 0 is read left to right, level 1 right to left, level 2 left to right again, and so on. Return a list of levels. Example: zigzag_level_order(build([3, 9, 20, None, None, 15, 7])) returns [[3], [20, 9], [15, 7]].

Quick quiz

Question 1

1.Which traversal records a node only after both of its subtrees?

Question 2

2.The tree is build([1, 2, 3, None, 4]). What is its inorder traversal?

Question 3

3.Why use collections.deque instead of a list for BFS in Python?

Question 4

4.In iterative preorder with a stack, why push the right child before the left child?

Question 5

5.A perfect binary tree has n nodes. What is the worst extra space of BFS on it?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which traversal records a node only after both of its subtrees?

Answer: Postorder

Postorder is left, right, node, so children are finished before the parent, which is what deleting or sizing needs.

The tree is build([1, 2, 3, None, 4]). What is its inorder traversal?

Answer: [2, 4, 1, 3]

Node 2 has only a right child 4. Inorder: left of 2 (nothing), 2, then 4, then root 1, then 3.

Why use collections.deque instead of a list for BFS in Python?

Answer: popleft() is O(1), while list.pop(0) shifts every item, O(n)

Taking from the front of a list moves all other items; a deque is built for both ends.

In iterative preorder with a stack, why push the right child before the left child?

Answer: So the left child is popped first, since a stack is last in, first out

The last item pushed is the first popped, so pushing left last makes the left subtree go first.

A perfect binary tree has n nodes. What is the worst extra space of BFS on it?

Answer: O(n), because the last level holds about n/2 nodes

The queue holds a whole level at a time, and the bottom level of a perfect tree has about half of all nodes.

Why is checking only node.left.val < node.val < node.right.val not enough to validate a BST?Related

Answer: A value deep in a subtree can break the rule with an ancestor, not its parent

In build([5, 4, 6, None, None, 3, 7]), 3 is fine next to 6 but is smaller than 5 while in 5's right subtree.

From Binary search trees

What does an in-order traversal of a BST produce?Related

Answer: Sorted order

Left, node, right.

From Binary search trees

In undirected cycle detection, DFS at vertex u sees its parent p, which is visited. What does that mean?Related

Answer: Nothing: it is the edge you came along

The edge u–p is the one used to reach u. Only a visited neighbour other than the parent means a cycle.

From Depth-first search (DFS)

Why should BFS mark a vertex as visited when it is added to the queue?Related

Answer: So the same vertex is not added to the queue many times

If you wait until it is popped, several neighbours can each add it before then.

From Breadth-first search (BFS)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Trees and binary search trees” before its exam: Recursive tree patterns, Binary search trees, Lowest common ancestor, serialisation and balanced trees.
  2. Module 7 exam: Trees and binary search trees · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Heaps and greedy algorithms. 61 topics left in the course before the final exam.

← Previous: Trees: words and shapes