If BFS is for levels, DFS is for paths and subtrees — and almost every tree DFS problem is the same three-line shape. Learn it once and a dozen questions become trivial.
The idea
DFS goes deep down one branch before backtracking, which recursion expresses perfectly (the call stack is the stack). The universal shape:
def dfs(node):
if not node:
return <base value> # empty subtree
L = dfs(node.left) # solve the left subtree
R = dfs(node.right) # solve the right subtree
return combine(node.val, L, R) # build this node's answer from the childrenYou only ever change two things: the base value and the combine step. O(n) time (each node once), O(h) space (recursion depth = tree height).
Four worked examples (same shape)
Max depth — combine is max(L, R) + 1:
def max_depth(root):
if not root: return 0
return 1 + max(max_depth(root.left), max_depth(root.right))Path sum (root-to-leaf summing to a target) — subtract as you go, check at a leaf:
def has_path_sum(root, target):
if not root: return False
if not root.left and not root.right: # leaf
return target == root.val
rem = target - root.val
return has_path_sum(root.left, rem) or has_path_sum(root.right, rem)Diameter (longest path between any two nodes) — a common trick: the recursion returns the height, but a side variable tracks the best left + right seen:
def diameter(root):
best = 0
def height(node):
nonlocal best
if not node: return 0
L = height(node.left); R = height(node.right)
best = max(best, L + R) # path through this node
return 1 + max(L, R) # height to return upward
height(root)
return best
# diameter of (1 / (2/(4,5)), 3) -> 3Validate BST — pass bounds down so each node knows its allowed range:
def is_valid_bst(root, lo=float("-inf"), hi=float("inf")):
if not root: return True
if not (lo < root.val < hi): return False
return (is_valid_bst(root.left, lo, root.val) and
is_valid_bst(root.right, root.val, hi))Notice the two flavours: some problems combine results coming up (depth, diameter, sum), others pass constraints down (validate-BST bounds). Many do both. Lowest-common-ancestor bubbles up “where did I find the targets?“.
Step-by-step walkthrough
Trace Maximum Depth on the same tree (3 → (9, 20), 20 → (15, 7)). DFS goes all the way down first; each node returns only after both children have reported (post-order), so the answers bubble up from the leaves.
| node returns | left depth | right depth | computation |
|---|---|---|---|
| 9 | 0 | 0 | 1 + max(0, 0) = 1 |
| 15 | 0 | 0 | 1 + max(0, 0) = 1 |
| 7 | 0 | 0 | 1 + max(0, 0) = 1 |
| 20 | 1 | 1 | 1 + max(1, 1) = 2 |
| 3 | 1 | 2 | 1 + max(1, 2) = 3 |
Read top to bottom, this is the order the calls finish: leaves first, root last. The root returns 3. The same recurse–combine–return shape solves diameter, path sums, and balanced-tree checks.
When to reach for it
Signals: the answer is about a path (root-to-leaf, node-to-node), a subtree property (height, balanced, count), an ancestor relationship (LCA), or a structural check (is it a valid BST, are two trees the same, invert/mirror). Anything recursive on a tree where “the answer here depends on the answers below” is DFS.
Pitfalls
- Missing the base case. Always handle
Nonefirst, or you’ll dereference null. - Confusing “return up” with “pass down”. Decide whether information flows up (combine children) or down (constraints like BST bounds) — mixing them is a classic bug (diameter needs the side variable precisely because the return value and the answer differ).
- Deep recursion. Very deep/skewed trees can hit recursion limits; mention an iterative stack version if asked.
Recap
Tree DFS is recursion with one shape: base case → recurse left → recurse right → combine. Change only the base value and the combine step and you get max-depth, path-sum, diameter, validate-BST and LCA. Information flows up (combine children) or down (pass constraints) — know which your problem needs. O(n) time, O(h) space. Reach for DFS on paths, subtrees and structure; BFS on levels.
Practice: 13 solved problems on tree DFS → — cover each one, solve it yourself, then check the tested code.
Next: Lesson 14 — Heaps / Top-K.