Intermediate: core algorithms · Trees and binary search trees · topic 32 of 61 · 35 min
Traversals: DFS and BFS
Explanation
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.
| Order | Visit | Result for build([1, 2, 3, 4, 5, None, 6]) | Typical use |
|---|---|---|---|
| Preorder | node, left, right | 1, 2, 4, 5, 3, 6 | Copy or serialise a tree; print a folder listing |
| Inorder | left, node, right | 4, 2, 5, 1, 3, 6 | Sorted output of a BST (Topic 4) |
| Postorder | left, right, node | 4, 5, 2, 6, 3, 1 | Delete a tree; compute sizes or heights from children up |
| Level order | level by level, left to right | 1, 2, 3, 4, 5, 6 | Shortest number of steps; print by level |
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]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.
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.
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
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.
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 += 1Practice (0/4 done)
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].
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].
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]].
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Recursive tree patternsTrees and binary search trees · same module
- Binary search treesTrees and binary search trees · same module
- Binary search treesC Programming (from zero to systems) · Data structures in C
- Depth-first search (DFS)Graphs
- Breadth-first search (BFS)Graphs
- Core DSA patterns in C++C++ Programming (modern C++ and DSA) · Competitive programming, interviews and careers
Questions people ask about this
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
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.
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.
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
- 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.
- Module 7 exam: Trees and binary search trees · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Heaps and greedy algorithms. 61 topics left in the course before the final exam.