Lowest common ancestor
Find the deepest node that has both of two given nodes below it (or is one of them).
O(n)Space O(h)6 practice problemsStep-through pictureThe idea
An ancestor of a node is any node on the path from the root down to it: its parent, its parent's parent, and so on. A node counts as its own ancestor here. Two nodes always share at least one ancestor, the root.
The lowest common ancestor, or LCA, is the shared ancestor that is deepest in the tree, the one closest to both nodes. In a DOM tree, it is the smallest element that contains both elements.
A recursive walk finds it in one pass. Each call asks its subtree: "Do you contain p or q?" If the left subtree finds one and the right subtree finds the other, the current node is where their paths split, so it is the answer. If only one side finds something, that result is passed up unchanged.
An everyday example
Finding the shared grandparent at a family reunion
Two cousins want to know their closest shared relative. Each family branch reports upward: "I have one of the cousins" or "I have nobody". A person whose left family branch reports one cousin and whose right branch reports the other is the closest relative they share. If a person is one of the cousins themselves, they simply report "found one" upward.
| In the example | In the code |
|---|---|
| Asking each family branch | lowestCommonAncestor(root.left, p, q) and the same on the right |
| "I have nobody" | a call that returns null |
| "I am one of the cousins" | root.val === p || root.val === q, so return root |
| Both branches found a cousin | left !== null && right !== null, so return root |
From the slow way to lowest common ancestor
- The obvious way: find the path from the root to
pand the path from the root toq, store both as arrays, and walk them side by side until they differ. The last shared node is the answer. That takesO(n)time, but it needs two searches and two extra arrays. - Look at what each subtree could tell its parent instead. A subtree contains neither node, one of them, or both. If a node hears "I have one" from its left side and "I have one" from its right side, the two paths split exactly at that node, so it's the lowest common ancestor. No path arrays are needed, only those answers.
- So let each call return a node:
nullfor "neither", the found node for "one", and the answer itself once it has been found lower down. A node that matchesporqcan return itself at once: if the other target is below it, it is the answer, and if not, a higher node will meet the other target on its other side. - Combining is then one line. If both sides returned something, return the current node; otherwise pass up whichever side found something. One postorder walk, each node asking its two children, is the recursive lowest common ancestor.
How it works
- If
rootisnull, this subtree has neither node: returnnull. - If
rootholdsporq, returnroot. Anything below cannot be lower and still contain this node. - Ask the left subtree:
left = lowestCommonAncestor(root.left, p, q). - Ask the right subtree:
right = lowestCommonAncestor(root.right, p, q). - If both
leftandrightare notnull,pis on one side andqon the other, sorootis the LCA. - Otherwise return whichever side is not
null, passing the found node (or the LCA found lower down) up to the parent.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function lowestCommonAncestor(root, p, q) {if (root === null) return null;if (root.val === p || root.val === q) return root;const left = lowestCommonAncestor(root.left, p, q);const right = lowestCommonAncestor(root.right, p, q);if (left !== null && right !== null) return root;return left !== null ? left : right;}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(h) |
In the worst case every node is visited once. The recursion keeps one call per level of the current path, up to the height h.
- Best case
O(1)porqis the root itself, so line 3 returns on the very first call. - Average case
O(n)In a typical tree the walk visits most nodes, because it can only skip the subtrees below a match.
- Worst case
O(n)Both targets are deep leaves, or one of them is missing, so every node is visited once.
Common mistakes
- Assuming both nodes are in the tree when they may not be. The code then returns the one it found. Check both exist first if that can happen.
- Searching below a node that matches
pto look forq. It isn't needed: ifqis belowp, thenpis the answer anyway. - On a BST, searching both subtrees. Use the order rule to walk one path instead.
- Comparing nodes by value when values repeat. Then two different nodes look the same. Use unique ids or compare node objects.
When to use it
Look for these clues in a problem:
- The question asks for the closest shared parent, container, manager, or folder of two items.
- You need the distance between two nodes in a tree: it goes up to their LCA and back down.
- The tree is a search tree and you need where two values' search paths split.
- You must decide which of two elements contains the other, or what contains both.
Check yourself
Answer each one in your head first, then open it.
Why can the code stop at a node that matches p without looking for q below it?
If q is below it, this node is the answer anyway. If q isn't below it, the parent will find q on its other side and decide.
In the trace, why does the root return 12 and not itself?
Only its left side found anything. A node returns itself only when both of its sides report a hit, or when it matches a target.
How would you get the distance between 7 and 30 in the trace tree?
Find the answer node 12. 7 is 1 edge below it and 30 is 2 edges below it, through 23, so the distance is 3 edges.
What does the function return when neither value is in the tree?
null, because no call ever matches at line 3, and every combine step passes null up.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 6 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingmedium
Go further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The rule for what a call on a subtree returns: if the subtree holds both p and q, it returns their lowest common ancestor; if it holds exactly one, it returns that node; if it holds neither, it returns null. An empty subtree holds neither, so returning null for it is right.
Check each case at a node root. If root is p or q, then either the other target is below it, making root the answer, or it isn't, and root is the one target this subtree holds; returning root is right both ways. Otherwise, if both children return something, each side must hold exactly one target, since a side holding both would leave the other side with none, so the paths split at root. If only one side returns something, that side holds everything this subtree has, so passing its answer up is right.
The walk ends because it's a DFS with a null base case. By the rule, the call on the whole tree returns the lowest common ancestor, as long as both targets really are in the tree. If only one is, the rule says the call returns that one node, which is why the code alone can't tell "found the answer" from "found only p".
Going deeper
4 more ideas
Why the first node with a hit on both sides is the answer
Below the LCA, p and q are in different subtrees (or one of them is the LCA itself). Every ancestor above the LCA sees both nodes coming from the same side, so it never returns itself; it only passes the LCA up. So the node returned at the top is exactly the deepest shared ancestor.
The easier case: a binary search tree
In a BST you don't need to search both sides. If both values are smaller than node.val, the LCA is on the left; if both are bigger, it is on the right. The first node where they go different ways (or where one of them equals node.val) is the LCA. That costs only O(h).
Time, memory, and a caution
The walk visits each node at most once: O(n) time and O(h) stack memory. It assumes both p and q are in the tree. If one may be missing, the code returns the other node as if it were the LCA, so first check that both exist, or count how many were found.
With parent pointers
DOM nodes know their parentNode. Then you can walk up from p and store each ancestor in a set, then walk up from q until you hit a stored node. That first hit is the LCA, found in O(h) time without visiting the rest of the tree.
Work it by hand
Every step in a table, with why
Find the lowest common ancestor of 7 and 30 in an 8-node tree. The answer is not the root, so the trace shows a result being passed up.
root = [40, 12, 55, 7, 23, null, 61, null, null, 18, 30], p = 7, q = 30
| Step | Call on | left | right | Returns | What happens | Why |
|---|---|---|---|---|---|---|
| 1 | 7 | - | - | 7 | matches p | The walk goes 40, then 12, then 7. 7 equals p, so line 3 returns it without looking below it. |
| 2 | 18 | null | null | null | nothing found | In 12's right subtree, the walk reaches 23 and then 18. 18 is neither value, and both its children are null. |
| 3 | 30 | - | - | 30 | matches q | 30 equals q, so it returns itself. |
| 4 | 23 | null | 30 | 30 | pass 30 up | Only the right side of 23 found something, so 23 passes 30 up unchanged. |
| 5 | 12 | 7 | 30 | 12 | paths split here | 12 hears 7 from its left and 30 from its right. The paths to p and q split here, so it returns itself. |
| 6 | 61 | null | null | null | nothing found | The walk now checks the root's right side. 61 is a leaf that matches neither value. |
| 7 | 55 | null | null | null | nothing found | 55 has no left child, and its right side found nothing. |
| 8 | 40 | 12 | null | 12 | pass 12 up | The root hears only from its left side, so it passes 12 up. A node above the answer never sees hits on both sides. |
12. The walk made 8 calls on real nodes, one per node, because both targets are leaves here. If 7 had children, the walk would not have gone into them. The trace ends when the call on the root returns.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Lowest common ancestor | O(n) | O(h) | One or a few questions on a plain tree with child pointers only. |
| Search tree walk | O(h) | O(1) | The tree is a BST, so the values tell you which way to go. |
| Parent pointers and a set | O(h) | O(h) | Nodes know their parents, like DOM elements, and you don't want to scan the whole tree. |
| Binary lifting | O(log n) | O(n log n) | Many questions on a fixed tree; pay O(n log n) once to build the table. |
Extra memory: O(h) for the recursion: one waiting call per level of the current path. A chain of n nodes needs n calls at once.
How the steps grow
- Lowest common ancestor, Search tree walk, Parent pointers and a set
O(n) - Binary lifting
O(log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Lowest common ancestor | 10 | 1,000 | 1 million |
| Search tree walk | 10 | 1,000 | 1 million |
| Parent pointers and a set | 10 | 1,000 | 1 million |
| Binary lifting | 3 | 10 | 20 |
Inputs that need care
5 tricky inputs
pandqare the same value- The first node that matches returns itself, so the answer is that node. This fits the definition, because a node counts as its own ancestor.
- One target is above the other, like
p = 12,q = 30in the trace tree - The call on
12returns at line 3 without searching below, and nothing else matches, so12comes back up as the answer, which is correct. qis not in the tree- The code returns
p's node as if it were the answer. If that can happen, count the matches first, or return{ node, count }and trust the answer only whencountis 2. - Repeated values in the tree
root.val === pmatches the first copy the walk reaches, which may be the wrong node. Pass node objects and compare withroot === pwhen values can repeat.- A very deep tree
- The recursion depth equals the height. For trees that can be thousands of levels deep, use parent pointers or an explicit stack.
Variations and follow-ups
Twists interviewers add
Search tree version
In a BST, compare both values with node.val: if both are smaller, go left; if both are bigger, go right; otherwise this node is the split point. It walks one path, O(h) time, with O(1) memory as a loop.
Parent pointers
With a parent link on each node, walk up from p and put every ancestor in a set, then walk up from q until you meet one. Or lift the deeper node to the same depth and step both up together until they meet, which needs O(1) memory.
Many questions on one tree
If you'll ask thousands of these questions on the same tree, build a binary lifting table once in O(n log n), then answer each one in O(log n).
Common ancestor of many nodes
The same recursion works for a set of targets: return the node when it's in the set, and return root when two or more of its children report a hit. The result is the deepest node that contains them all.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "common ancestor", "smallest subtree containing both", "closest shared container", or "distance between two nodes", which is
depth(p) + depth(q) - 2 * depth(lca). - Name the three possible return values before coding:
null, one target, or the answer. The last two lines of the function are then easy to explain. - Ask whether both nodes are sure to exist and whether the tree is a BST. Each answer changes the best solution, and asking shows you know that.
- Test by hand: two siblings, one node above the other, and two nodes on opposite sides of the root. The second case is where most wrong versions break.
Where frontend code uses it
The same idea in real UI code
- The DOM
RangeAPI has acommonAncestorContainerproperty: the deepest node that contains both ends of a text selection. It is the LCA of the selection's start and end. Node.compareDocumentPositionandNode.containsanswer "is this node inside that one?", the building blocks for walking up to a shared ancestor.- To position a shared tooltip or highlight around two elements, you often need the smallest element that contains both.
- In a file explorer, the deepest folder containing two selected files is their LCA, which is where a "move together" or "shared path" starts.