Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Two pointers, one per list. When a pointer hits the end, redirect it to the *other* list's head. After at most one switch each, both pointers have walked lenA + lenB nodes and meet exactly at the intersection node — or both reach null together if the lists never merge. O(n + m) time, O(1) space, no length math needed.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you compare nodes by reference rather than value, and whether you can equalize two different list lengths — either by switching heads or by advancing the longer list first — without brute-forcing all pairs.
Where candidates slip
Comparing a.val == b.val instead of a == b (a node identity), or building the length-difference solution off-by-one so the pointers never align.
How to stand out
Lead with the switching-heads two-pointer for O(1) space, explain *why* both pointers walk lenA + lenB, and mention the hash-set and length-difference alternatives as trade-offs.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewFind merge point of two listsO(1) space required?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Hash Set — Remember One List's Nodes (Baseline)
1Hint 1Explore hintHide hint
If you wrote down every node of list A, how would you then test each node of list B?
2Hint 2Explore hintHide hint
Store node references (not values) in a hash set as you walk A.
3Hint 3Explore hintHide hint
Walk B and return the first node already in the set — that's the first shared node, the intersection.
A · Core idea
We record the identity of every node in one list, then scan the other for the first match.
Why store nodes, not values. Intersection is defined by identity, so the set must hold node references. If we stored values, two distinct nodes holding the same number would falsely register as a merge. In Java that's an IdentityHashMap-style set or just a HashSet (default identity hashCode); in Python, object id semantics make set() of nodes do the right thing.
Why the first hit in B is the answer. Because a singly linked list can't split, the moment list B steps onto a node that belongs to A, every following node is shared too. So the first node of B found in the set is precisely the merge point.
Why we still want better. This is correct and trivially explainable, but it costs O(n) memory. Interviewers usually push for O(1) space, which the two-pointer switch achieves. Lead with this to show the idea, then optimize.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
A = [4, 1, 8], B = [6, 8], merge at node 8Hash-map state
scan A
n8Continue scanning
insert n4, n1, n8
node with val 8C · Code with teaching comments
Reference implementation previewpublic class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; }Explore the full codeHide the full code
D · Why it works and complexityO(n + m) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Membership testing turns 'do these paths share a node?' into 'have I seen this exact node before?' — the same remember-and-probe shape as Two Sum, applied to references instead of values.
ComplexityO(n + m) time, O(n) space — One pass over A to fill the set (n inserts), one pass over B to probe (m lookups), each O(1) average → O(n + m) time. The set holds up to n node references → O(n) space.
E · Edge cases and pitfalls5A = [], B = [1] — Set is empty; probing B finds nothing → null.Explore edge casesHide edge cases
Edge cases handled
A = [], B = [1]Set is empty; probing B finds nothing → null.A = B (same head)Every node of B is in the set; first node returned is the shared head.
Pitfalls
- Storing values instead of node references — two equal values falsely look like an intersection.
- Probing A against B but inserting B (works, but be consistent) — pick one list to record.
- Assuming O(1) space — this is O(n), which is exactly what the two-pointer version fixes.
Approach 2 of 2 · Optimal
Two Pointers — Switch Heads at the End (Optimal)
1Hint 1Explore hintHide hint
The only thing stopping a simple side-by-side walk is that the two lists have different lengths before the join. How could you make both pointers travel the *same* total distance?
2Hint 2Explore hintHide hint
Let each pointer, on reaching the end of its list, jump to the head of the OTHER list. How far has each pointer traveled when they meet?
3Hint 3Explore hintHide hint
After the switch, pointer A walks lenA + lenB and pointer B walks lenB + lenA — equal totals — so they arrive at the intersection on the same step. If there's no intersection, they hit null together.
A · Core idea
We run one pointer down each list; whenever a pointer runs off the end, we send it to the other list's head. After the swap the pointers are length-aligned and meet at the join.
The length problem, and the fix. If list A is shorter, a pointer at A's head and one at B's head are misaligned: when both step together, A's pointer reaches the shared tail 'too early'. The switch fixes this without measuring anything. Pointer a walks lenA, then crosses to B and walks lenB_before_merge to reach the join — total lenA + (lenB_before). Pointer b walks lenB, crosses to A, walks lenA_before — total lenB + (lenA_before). Since lenA = lenA_before + shared and lenB = lenB_before + shared, both totals equal lenA_before + lenB_before + shared = lenA + lenB_before = lenB + lenA_before. Equal distances → they land on the intersection together.
Compare by identity. The loop condition is a != b comparing node references. When they point at the same physical node, that node is the merge point. Never compare a.val == b.val — distinct nodes can share a value.
Why the no-intersection case terminates. If the lists never merge, each pointer walks lenA + lenB and then becomes null. Crucially, both become null on the same step (they've each traveled lenA + lenB), so a == b holds with both being null and the loop exits returning null — no infinite loop.
Why redirect on the step after null, not skip null. The trick relies on each pointer 'spending' exactly one step at the boundary. Reading a = a.next if a else headB means when a is null we send it to headB; the symmetric null arrivals are what guarantee the equal-distance meeting (including the both-null exit).
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
A = [4, 1, 8], B = [6, 1, 8], merge at node 8 (shared tail of length 1)Interactive diagram
Why switching heads aligns the pointers
startBoth pointers traverse lenA + lenB nodes, so they arrive at the merge node on the same step.
node with val 8 (the shared node)C · Code with teaching comments
D · Why it works and complexityO(n + m) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Switching heads is a way to make two unequal lengths equal for free — no counting, no second pass to measure. The named trap is comparing values instead of node identity; the second trap is sending a null pointer back to its own head instead of the other's, which breaks the length cancellation.
ComplexityO(n + m) time, O(1) space — Each pointer traverses at most lenA + lenB nodes before meeting or both reaching null → O(n + m) time. Only two pointers are used → O(1) space.
E · Edge cases and pitfalls7A = [2, 6, 4], B = [1, 5] (no merge) — Each pointer walks 5 nodes then both become null on the same step → a == b == null → return null.Explore edge casesHide edge cases
Edge cases handled
A = [2, 6, 4], B = [1, 5] (no merge)Each pointer walks 5 nodes then both become null on the same step → a == b == null → return null.Same length, merge at heada and b are equal from the first comparison (no switch needed) → return the shared head.A = [3], B = [3] distinct nodes, no mergeEqual values but different nodes; pointers switch and both reach null together → null.
Pitfalls
- Comparing a.val == b.val instead of node identity — false matches on repeated values.
- On reaching null, jumping to the SAME list's head instead of the other — the length difference never cancels and you loop forever.
- Skipping null instead of spending one step on it — breaks the equal-distance guarantee, including the both-null exit.
- Assuming the lists are cyclic — this method requires acyclic lists; detect/remove cycles first.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) return null; ListNode a = headA; ListNode b = headB; while (a != b) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Comparing values instead of identity
Intersection means the same physical node. Two different nodes can hold equal values (e.g. both 8), so a value comparison reports a false merge before the real one.
Explore the code comparisonHide the code comparison
Comparing values instead of identity
Intersection means the same physical node. Two different nodes can hold equal values (e.g. both 8), so a value comparison reports a false merge before the real one.
Don't write this
while (a != null && b != null) {
if (a.val == b.val) return a; // false positive on repeated values
a = a.next; b = b.next;
}Write this instead
while (a != b) { // reference comparison
a = (a == null) ? headB : a.next;
b = (b == null) ? headA : b.next;
}
return a;2Switching to the wrong head
The length cancellation only works if a finished pointer crosses into the OTHER list. Sending it back to its own head keeps the same length and the pointers never align — an infinite loop on intersecting lists.
Explore the code comparisonHide the code comparison
Switching to the wrong head
The length cancellation only works if a finished pointer crosses into the OTHER list. Sending it back to its own head keeps the same length and the pointers never align — an infinite loop on intersecting lists.
Don't write this
a = headA if a is None else a.next # wrong: back to own head
b = headB if b is None else b.nextWrite this instead
a = headB if a is None else a.next # cross to the other list
b = headA if b is None else b.next3Off-by-one in the length-difference method
An alternative is to advance the longer list by |lenA − lenB| first, then walk together. Miscounting the difference (or which list is longer) shifts the alignment so the pointers pass the intersection without meeting.
Explore the code comparisonHide the code comparison
Off-by-one in the length-difference method
An alternative is to advance the longer list by |lenA − lenB| first, then walk together. Miscounting the difference (or which list is longer) shifts the alignment so the pointers pass the intersection without meeting.
Don't write this
diff = lenA - lenB
for _ in range(diff): # negative when B is longer → no-op, misaligned
a = a.nextWrite this instead
diff = abs(lenA - lenB)
longer = a if lenA > lenB else b
for _ in range(diff):
longer = longer.nextRevise
Carry the pattern into the interview
Pattern to remember
Switching-heads two pointers (equalize path lengths by swapping lists)
Code pattern to memorize
a, b = headA, headB
while a is not b:
a = a.next if a else headB
b = b.next if b else headA
return a # the intersection node, or nullRules to remember
- 1Compare nodes by identity (
a == b/a is b), never by value — two equal values aren't the same node. - 2When a pointer hits null, redirect it to the other list's head — not its own — to cancel the length difference.
- 3Redirect to the head on the step after reaching null, so each pointer walks exactly lenA + lenB and they align.
- 4If the lists don't intersect, both pointers become null on the same step and
a is b(both null) ends the loop → return null. - 5Do not mutate the lists; this is a read-only traversal (no cycles are created).
Use this pattern when
- Find where two singly linked lists join into a shared tail (Y-shape)
- Detect a shared suffix without knowing the lengths in advance
- Any 'two paths converge' problem on pointer-based structures
- When O(1) extra space is required and you can't use a hash set
Choose another pattern when
- The lists may form a cycle → handle the cycle first (Floyd), this assumes acyclic Y-shape
- You can mutate freely and want the simplest code → a hash set of one list's nodes is more obvious
- You only need whether they intersect, not the node → still works, but a set answers it too
- Nodes can be compared by value uniquely → still compare by reference; values may repeat
Interview takeaway
The switching-heads trick equalizes two unequal path lengths without ever measuring them: after each pointer crosses into the other list, both have lenA + lenB to walk, so they land on the join together. It's the cleanest O(1)-space answer and the same length-alignment idea behind many two-pointer problems.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternTwo-pointer length alignment via head switching: equalize two unequal path lengths without measuring them, so the pointers converge at the shared node. The same convergence idea drives cycle-entry detection and any 'two traversals must meet' problem.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Linked List CycleTwo-pointer idea again, but fast/slow within one list to detect a loop instead of a shared tail.
- Linked List Cycle IIFind the cycle's entry node — the same 'where do paths converge' question, solved with pointer math.
- Intersection of Two Linked ListsThe canonical LeetCode framing of this exact problem.