Templates memorized? Now let's apply them to real interview problems. Each example includes a step-by-step dry run, full code in three languages, and the key insight that interviewers look for.
Worked Examples
Reverse Linked List
EasyLC 206Problem
Given the head of a singly linked list, reverse the list, and return the reversed list.
Approach: Iterative Three-Pointer
This is the canonical linked list reversal — the foundation for dozens of harder problems. The idea is simple: walk through the list and redirect each .next pointer to point backward instead of forward. The three pointers (prev, curr, next_node) ensure we never lose access to the rest of the list.
| Step | prev | curr | next | Action |
|---|---|---|---|---|
| 0 | null | 1 | — | Initialize |
| 1 | 1 | 2 | 2 | 1.next = null. Advance prev=1, curr=2 |
| 2 | 2 | 3 | 3 | 2.next = 1. Advance prev=2, curr=3 |
| 3 | 3 | 4 | 4 | 3.next = 2. Advance prev=3, curr=4 |
| 4 | 4 | 5 | 5 | 4.next = 3. Advance prev=4, curr=5 |
| 5 | 5 | null | null | 5.next = 4. Done! Return prev = 5 |
1class Solution:2 def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:3 prev = None4 curr = head56 while curr:7 next_node = curr.next # Save next8 curr.next = prev # Reverse the link9 prev = curr # Move prev forward10 curr = next_node # Move curr forward1112 return prev # prev is the new head1314 # --- Recursive approach (alternative) ---15 def reverseListRecursive(self, head: Optional[ListNode]) -> Optional[ListNode]:16 # Base case: empty list or single node17 if not head or not head.next:18 return head1920 # Recurse: reverse the rest of the list21 new_head = self.reverseListRecursive(head.next)2223 # head.next is the last node in the reversed portion24 # Make it point back to us25 head.next.next = head26 head.next = None # Break the forward link2728 return new_head # Propagate the new head upTime
O(n)
Visit each node once
Space (iterative)
O(1)
Only 3 pointers
Space (recursive)
O(n)
Call stack depth
Key Insight
Merge Two Sorted Lists
EasyLC 21Problem
Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.
Approach: Dummy Head + Comparison
Use a dummy head node so we never need special-case logic for the first appended node. Then compare the heads of both lists, append the smaller one, and advance that list's pointer. When one list is exhausted, attach the remainder of the other (already sorted, already linked).
| Step | l1 | l2 | Compare | Merged so far |
|---|---|---|---|---|
| 0 | 1→2→4 | 1→3→4 | — | dummy |
| 1 | 2→4 | 1→3→4 | 1 ≤ 1 | 1 |
| 2 | 2→4 | 3→4 | 1 ≤ 2 | 1→1 |
| 3 | 4 | 3→4 | 2 ≤ 3 | 1→1→2 |
| 4 | 4 | 4 | 3 ≤ 4 | 1→1→2→3 |
| 5 | null | 4 | 4 ≤ 4 | 1→1→2→3→4 |
| 6 | null | null | append remaining | 1→1→2→3→4→4 |
1class Solution:2 def mergeTwoLists(self, l1: Optional[ListNode],3 l2: Optional[ListNode]) -> Optional[ListNode]:4 dummy = ListNode(0)5 tail = dummy67 while l1 and l2:8 if l1.val <= l2.val:9 tail.next = l110 l1 = l1.next11 else:12 tail.next = l213 l2 = l2.next14 tail = tail.next1516 # Append whichever list still has nodes17 tail.next = l1 if l1 else l21819 return dummy.next2021# Why dummy head?22# Without it, you need: if not head: head = node; else: tail.next = node23# With it, EVERY append is just: tail.next = node; tail = tail.next24# Cleaner code = fewer bugs in an interview.Time
O(n + m)
Each node visited once
Space
O(1)
Reusing existing nodes
Key Insight
Add Two Numbers
MediumLC 2Problem
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each node contains a single digit. Add the two numbers and return the sum as a linked list.
Approach: Digit-by-Digit Addition with Carry
Since digits are in reverse order (least significant first), we can add them directly left to right, exactly like grade-school addition. Keep a carry variable. The key edge case: when both lists are exhausted but carry is 1 (e.g., 999 + 1 = 1000), we need an extra node.
Dry Run: 342 + 465 = 807 (stored as 2→4→3 + 5→6→4)
| Step | l1 digit | l2 digit | carry in | sum | new digit | carry out |
|---|---|---|---|---|---|---|
| 1 | 2 | 5 | 0 | 7 | 7 | 0 |
| 2 | 4 | 6 | 0 | 10 | 0 | 1 |
| 3 | 3 | 4 | 1 | 8 | 8 | 0 |
Result: 7 → 0 → 8 = 807
1class Solution:2 def addTwoNumbers(self, l1: Optional[ListNode],3 l2: Optional[ListNode]) -> Optional[ListNode]:4 dummy = ListNode(0)5 curr = dummy6 carry = 078 # Continue while either list has digits OR there's a carry9 while l1 or l2 or carry:10 val = carry11 if l1:12 val += l1.val13 l1 = l1.next14 if l2:15 val += l2.val16 l2 = l2.next1718 carry, digit = divmod(val, 10) # carry = val // 10, digit = val % 1019 curr.next = ListNode(digit)20 curr = curr.next2122 return dummy.next2324# Edge case: 999 + 1 = 100025# l1: 9->9->9, l2: 126# Step 1: 9+1+0=10 → digit=0, carry=127# Step 2: 9+0+1=10 → digit=0, carry=128# Step 3: 9+0+1=10 → digit=0, carry=129# Step 4: 0+0+1=1 → digit=1, carry=0 ← "or carry" catches this!30# Result: 0->0->0->1 = 1000Time
O(max(n, m))
Process all digits
Space
O(max(n, m))
New list for result
Common Mistake
or carry in the while condition is the #1 bug on this problem. When both lists are exhausted but carry = 1, you need one more node. Test with 999 + 1 to verify your solution handles this.Intersection of Two Linked Lists
EasyLC 160Problem
Given the heads of two singly linked lists, return the node at which the two lists intersect. If they do not intersect, return null.
Approach: Two-Pointer Redirect
The elegant insight: if pointer A walks through list A then list B, and pointer B walks through list B then list A, they both travel the same total distance: (length_A + length_B). They will reach the intersection node at the same time on their second pass. If no intersection exists, both reach null simultaneously.
List A: a1 → a2 → c1 → c2 → c3 (length: 2 unique + 3 shared = 5)
List B: b1 → b2 → b3 → c1 → c2 → c3 (length: 3 unique + 3 shared = 6)
pA travels: a1, a2, c1, c2, c3, b1, b2, b3, c1 (2+3+3 = 8 steps)
pB travels: b1, b2, b3, c1, c2, c3, a1, a2, c1 (3+3+2 = 8 steps)
Both arrive at c1 after 8 steps!
1class Solution:2 def getIntersectionNode(self, headA: ListNode,3 headB: ListNode) -> Optional[ListNode]:4 pA, pB = headA, headB56 # When a pointer reaches the end, redirect to the OTHER list's head.7 # Both pointers travel (lenA + lenB) total steps.8 # If they intersect, they meet at the intersection node.9 # If not, both reach None at the same time.10 while pA != pB:11 pA = pA.next if pA else headB12 pB = pB.next if pB else headA1314 return pA # Either the intersection node or None1516# Why this works:17# pA travels: lenA + lenB nodes total18# pB travels: lenB + lenA nodes total19# Same total distance → they reach the intersection simultaneously20# If no intersection: both reach null after lenA + lenB stepsTime
O(n + m)
Each pointer traverses both lists
Space
O(1)
Just two pointers
Key Insight
a + b + c nodes (where a and b are unique lengths, c is shared length). Equal distances at equal speeds means simultaneous arrival. This is an O(1) space solution that avoids counting lengths or using hash sets.Reorder List
MediumLC 143Problem
Given the head of a singly linked list L: L0 → L1 → … → Ln, reorder it to: L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → … You may not modify the values in the list's nodes. Only nodes themselves may be changed.
Approach: Three-Phase Technique
This problem is the “boss fight” of linked list patterns — it combines three techniques in one problem:
- Phase 1 — Find middle: Use fast/slow pointers to split the list into two halves.
- Phase 2 — Reverse: Reverse the second half so we can interleave from both ends.
- Phase 3 — Merge: Interleave nodes from the first and reversed second halves.
Dry Run: 1 → 2 → 3 → 4 → 5
Phase 1: slow=3, fast=5 → split at 3: first=[1,2,3], second=[4,5]
Phase 2: Reverse [4,5] → [5,4]
Phase 3: Interleave [1,2,3] and [5,4]:
1 → 5 → 2 → 4 → 3
Result: 1 → 5 → 2 → 4 → 3
1class Solution:2 def reorderList(self, head: Optional[ListNode]) -> None:3 if not head or not head.next:4 return56 # ---- PHASE 1: Find middle using fast/slow ----7 slow, fast = head, head8 while fast.next and fast.next.next:9 slow = slow.next10 fast = fast.next.next11 # slow is at the middle (end of first half)1213 # ---- PHASE 2: Reverse the second half ----14 second = slow.next # Start of second half15 slow.next = None # Cut the list in two16 prev = None17 while second:18 nxt = second.next19 second.next = prev20 prev = second21 second = nxt22 second = prev # prev is the new head of reversed second half2324 # ---- PHASE 3: Merge / interleave the two halves ----25 first = head26 while second:27 tmp1, tmp2 = first.next, second.next28 first.next = second # first -> second29 second.next = tmp1 # second -> next_first30 first = tmp1 # Advance first31 second = tmp2 # Advance second3233# Why three phases?34# - Phase 1 (fast/slow): Gives us the split point in O(n)35# - Phase 2 (reverse): Without reversal, we can't access Ln, Ln-136# efficiently — singly linked lists don't go backward37# - Phase 3 (merge): Interleave nodes from both halves38# Total: O(n) time, O(1) space — optimalTime
O(n)
Three linear passes
Space
O(1)
In-place rearrangement
Key Insight