One of the biggest struggles with tree recursion isn't writing the code—it's knowing how to think about the recursion in the first place.
Whenever I see a tree DFS problem, I don't try to memorize the solution.
Instead, I ask these 5 questions.
Every recursion needs a stopping condition.
Ask yourself:
What should I return if there is no node?
if not node:
return ...The return value depends on the problem.
Examples:
Maximum Depth -> 0
LCA -> None
Balanced Tree -> 0
Path Sum -> FalseBefore asking my children anything, I ask:
Can I solve the problem at the current node without looking below?
If yes, return immediately.
Example (LCA):
if node == p or node == q:
return nodeExample (Path Sum):
if not node.left and not node.right:
return target == node.valSome problems don't have a self case at all.
If I cannot answer yet, I need information from my children.
left = dfs(node.left)
right = dfs(node.right)Think of it like asking:
Left child,
what did you find?
Right child,
what did you find?Notice that nothing has been solved yet.
We're simply collecting information.
Now I'm the parent.
Both children have answered.
I combine their answers to solve my own subproblem.
Example: Maximum Depth
Children return:
heightSo I combine them using:
return 1 + max(left, right)Example: Lowest Common Ancestor
Children return:
None
p
q
LCASo I combine them like this:
if left and right:
return nodeExample: Diameter of Binary Tree
Children return:
heightParent updates:
diameter = max(diameter, left + right)Every tree problem has a different combine step.
Finally ask:
What information does my parent need from me?
This is the most important question.
Examples:
Maximum Depth
return 1 + max(left, right)Balanced Tree
return heightLowest Common Ancestor
return left or rightEvery recursive call is returning information upward.
That's why this is called bottom-up DFS.
def dfs(node):
# 1. Base case
if not node:
return ...
# 2. Self case
if ...:
return ...
# 3. Ask children
left = dfs(node.left)
right = dfs(node.right)
# 4. Combine children's answers
...
# 5. Return information to parent
return ...Ask yourself one question:
Can the parent solve the problem before the children finish?
If No, it's probably bottom-up DFS.
The parent must wait for information from the children before making its own decision.
| Problem | Children Return | Parent Combines |
|---|---|---|
| Maximum Depth | Height | 1 + max(left, right) |
| Diameter of Binary Tree | Height | Update diameter = left + right |
| Balanced Binary Tree | Height | Check abs(left - right) |
| Lowest Common Ancestor | None, p, q, or LCA | If both sides return non-null, current node is LCA |
| Binary Tree Maximum Path Sum | Maximum gain | Update global maximum path |
Notice that the template stays the same.
Only these two parts change:
Don't think:
"How do I solve the whole problem?"
Instead think:
"What information should each node return to its parent?"
Once you know what every recursive call should return, the DFS almost writes itself.
That's the mental model that helped recursion finally click for me, and I hope it helps someone else too.