Step 1 Understand it
Remember it by
Trust the recursion: solve it for the left and right children, then combine at the node.
Watch it happen
Read the animation as text
- How deep is the tree? Ask each node: "how deep is the tree below you?" A node waits for both children to answer, then returns 1 + the bigger answer. An empty child returns 0.
- 9 is a leaf: both children are empty (0). It returns 1 + max(0, 0) = 1.
- 15 is a leaf: both children are empty (0). It returns 1 + max(0, 0) = 1.
- 7 is a leaf: both children are empty (0). It returns 1 + max(0, 0) = 1.
- 20 has heard from both children: left 1, right 1. It returns 1 + max(1, 1) = 2.
- 3 has heard from both children: left 1, right 2. It returns 1 + max(1, 2) = 3.
- The root returns 3: the answer. Children answer before parents (post-order), and each node is visited once: O(n).
The idea
Most tree problems are depth-first: a function that handles one node by calling itself on the left and right children and combining their answers. Problems about 'levels' or 'the closest' use breadth-first search with a queue instead.
Picture it
flowchart TD
A(("3")) --> B(("9"))
A --> C(("20"))
C --> D(("15"))
C --> E(("7"))
B -.->|"returns 1"| A
C -.->|"returns 2"| AAnalogy
A manager asking two team leads for their team's number, adding their own, and passing the total up. Nobody needs to see the whole company.
Where the analogy breaks
Real managers can be asked twice. In recursion, the base case (an empty child returns 0, None or True) is what stops it — forget it and you crash.
More detail
What it is
A Tree is just like a Linked List, but instead of one 'next' pointer, a node can have multiple pointers (like 'left' and 'right'). It branches out exactly like a real tree! The most common type is a Binary Tree, where each node has at most two children. The very top node is called the 'root', and nodes with no children are called 'leaves'. Trees are incredible for representing hierarchical data, like file systems, company org charts, or family trees.
When to use it
- When data is naturally hierarchical.
- When you need to perform fast O(log N) searches, insertions, and deletions (using a Binary Search Tree).
- When you need to traverse data in a specific order (In-order, Pre-order, Post-order).
How it works
Because trees are recursive by nature (every child is the 'root' of its own smaller subtree), the most elegant way to work with them is using Recursion! You write a function that does something to the current node, and then simply calls itself on the left child and the right child. You'll almost always use Depth-First Search (DFS) with recursion, or Breadth-First Search (BFS) using a Queue to go level-by-level.
Example walkthrough
Input: root = [4,2,7,1,3], invert it
- At root 4: swap left (2) and right (7). Recursively call invert(2) and invert(7).
- At 2: swap 1 and 3.
- At 7: swap null and null.
- All subtrees inverted. The whole tree is now a mirror image!
Result: [4,7,2,null,null,3,1]
Related patterns
- Depth-First Search (DFS): The standard recursive way to go deep into a tree before coming back up.
Step 2 Recognise it
Words that signal it
- The input is a binary tree (TreeNode)
- 'Depth', 'height', 'diameter', 'balanced', 'path sum' → DFS
- 'Level by level', 'right side view', 'minimum depth' → BFS
- 'Binary search tree': left < node < right
Don't confuse it with
- Graphs: A tree has no cycles and one parent per node, so no visited set is needed. If a node can be reached two ways, it's a graph.
- Backtracking: Listing every root-to-leaf path is backtracking on a tree; computing one value from the children is plain DFS.
Gut check
Can I answer for a node if I knew the answer for its children? → recursive DFS. Levels? → BFS.
How to recognise it in an interview
The input is a TreeNode. The problem asks about 'depth', 'lowest common ancestor', or 'level order'.
Step 3 The template
The general shape. Adapt it to each problem rather than learning one solution per problem.
def dfs(node):
if not node: # base case
return 0
left = dfs(node.left) # trust it: the answer for the left subtree
right = dfs(node.right) # and for the right subtree
return 1 + max(left, right) # combine at this nodeThe same template in Java
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
// Swap the children
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
// Recursively do the same for the rest of the tree
invertTree(root.left);
invertTree(root.right);
return root;
}
}BFS level by level
from collections import deque
def levels(root):
out, q = [], deque([root] if root else [])
while q:
level = []
for _ in range(len(q)): # exactly one level
node = q.popleft()
level.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
out.append(level)
return outCommon mistakes
- Missing the base case if not node: return ….
- Checking BST validity only against the direct parent instead of passing down min and max bounds.
- Mixing what a function returns with the global answer (Diameter: return the height, update the diameter separately).
- Not trusting the recursion! Beginners often try to manually track tree depth with complicated loops instead of just letting the call stack do the work.
Step 4 Watch it run
Two problems, traced step by step exactly as the code runs.
Easy example: Maximum Depth of Binary Tree
Input: root = [3, 9, 20, null, null, 15, 7]
| node | left depth | right depth | returns |
|---|---|---|---|
| 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 |
Result: 3
Notice: The order of the rows is the order the calls finish: children always finish before their parent (post-order).
def max_depth(root):
if not root:
return 0
return 1 + max(max_depth(root.left), max_depth(root.right))Medium example: Binary Tree Level Order Traversal (BFS)
Input: root = [3, 9, 20, null, null, 15, 7]
| queue at start | level size | this level | queue after |
|---|---|---|---|
| [3] | 1 | [3] | [9, 20] |
| [9, 20] | 2 | [9, 20] | [15, 7] |
| [15, 7] | 2 | [15, 7] | empty |
Result: [[3], [9, 20], [15, 7]]
Notice: Freezing len(q) at the start of each round is what separates one level from the next.
from collections import deque
def level_order(root):
out, q = [], deque([root] if root else [])
while q:
level = []
for _ in range(len(q)):
node = q.popleft()
level.append(node.val)
if node.left: q.append(node.left)
if node.right: q.append(node.right)
out.append(level)
return outStep 5 Practise
Read each question and say the pattern and the trick out loud before you open the answer. Then solve it on LeetCode. Tick a problem once you've solved it on your own: it comes back for review tomorrow, then after 3, 7, 14 and 30 days, then every month, on the DSA page.
Easy
-
Invert Binary Tree Easy
Mirror a binary tree (swap every left and right child).
Show answer
Technique: DFS recursion
The clue: Do the same simple thing at every node.
The trick: Swap the children, then invert both subtrees.
-
Return the number of nodes on the longest root-to-leaf path.
Show answer
Technique: DFS returning height
The clue: 'Depth' = combine the children's answers.
The trick: Depth = 1 + max(left depth, right depth); an empty tree is 0.
-
Find the longest path (in edges) between any two nodes.
Show answer
Technique: DFS returning height, global best
The clue: The longest path bends at some node: left height + right height.
The trick: Return the height from each call, and update a global best with left + right at every node.
-
Balanced Binary Tree Easy
Decide whether every node's two subtree heights differ by at most 1.
Show answer
Technique: DFS returning height (or −1 for unbalanced)
The clue: A height check at every node.
The trick: Return the height if balanced, or −1 to signal 'unbalanced' all the way up, so it's O(n).
-
Same Tree Easy
Decide whether two binary trees are identical.
Show answer
Technique: DFS on both trees together
The clue: Compare structure and values node by node.
The trick: Both empty → true; one empty or values differ → false; else recurse on left and right pairs.
-
Decide whether one tree appears exactly as a subtree of another.
Show answer
Technique: DFS + Same Tree check
The clue: 'Appears somewhere inside' = try every node as a starting point.
The trick: At every node of the big tree, run Same Tree with the small one.
Medium
-
In a binary search tree, find the lowest node that has both p and q below it (or is one of them).
Show answer
Technique: BST property walk
The clue: It's a binary search tree: values tell you which way to go.
The trick: If both values are smaller, go left; both bigger, go right; otherwise you've found the split point.
-
Return the node values level by level.
Show answer
Technique: BFS with a queue
The clue: 'Level by level'.
The trick: Process exactly len(queue) nodes per round; that's one level.
-
Binary Tree Right Side View Medium
Return the values you'd see looking at the tree from the right.
Show answer
Technique: BFS, last node of each level
The clue: 'What you see from the side' = one node per level.
The trick: Do a level-order traversal and keep the last value of each level.
-
Count nodes that are at least as large as every node on the path from the root to them.
Show answer
Technique: DFS passing the path maximum down
The clue: A condition about the path from the root.
The trick: Pass the largest value seen so far into each call; a node is good if it's ≥ that value.
-
Validate Binary Search Tree Medium
Decide whether a binary tree is a valid binary search tree.
Show answer
Technique: DFS with min and max bounds
The clue: Every node must fit the rules of all its ancestors, not just its parent.
The trick: Pass an allowed range (low, high) down; going left lowers high, going right raises low.
-
Return the k-th smallest value in a binary search tree.
Show answer
Technique: In-order traversal
The clue: In a BST, in-order visits values in sorted order.
The trick: Do an in-order traversal (iteratively with a stack) and stop at the k-th value.
-
Rebuild a binary tree from its preorder and inorder lists.
Show answer
Technique: Recursion with an index map
The clue: Preorder gives roots; inorder splits left from right.
The trick: The first preorder value is the root; its position in inorder tells you the size of the left subtree. Use a value → index map.
Hard
-
Find the largest sum of any path between two nodes.
Show answer
Technique: DFS returning the best one-sided path, global best
The clue: A path bends at most once, like the diameter.
The trick: Each call returns node + max(0, best child path); update the global best with node + left gain + right gain.
-
Turn a binary tree into a string and rebuild it exactly.
Show answer
Technique: Preorder DFS with null markers
The clue: You need the shape, not just the values.
The trick: Write values in preorder with 'N' for empty children; read them back with the same recursion.
What to say in the interview
'Because trees are inherently recursive structures, I will use Depth-First Search to elegantly traverse the nodes in O(N) time.'
Follow-up prep
Do you know the difference between Pre-Order, In-Order, and Post-Order traversals?