Interview Pattern Catalog
Interviewers do not invent new linked-list problems; they re-skin eight of them. Once you can read a problem statement and name the pattern in the first thirty seconds, the coding is mechanical — you already wrote the template in Module 2. This post is the mapping from words on the whiteboard to the technique that solves them: the eight recurring patterns with their tells, templates, and traps; a decision tree you can run in your head; and the part candidates neglect — how to actually conduct the round so that a correct solution also reads as a strong one.
The Eight Recurring Patterns
Each pattern below gives you four things: the tell (the phrase in the statement that triggers it), the template (code you can reproduce cold), the problems it solves, and the trap (the mistake that fails it under pressure).
1. Dummy Head / Sentinel
Tell: "you may need to modify the head", "remove all nodes equal to…", any deletion where the first node might go.
A sentinel node in front of the list means the real head is never a special case — prev always exists.
ListNode dummy(0);
dummy.next = head;
ListNode* prev = &dummy;
while (prev->next) {
if (shouldRemove(prev->next))
prev->next = prev->next->next; // unlink; prev stays put
else
prev = prev->next;
}
return dummy.next;
Solves: 203 Remove Elements, 82 Remove Duplicates II, 19 Remove n-th From End, 2 Add Two Numbers.
Trap: returning head instead of dummy.next (the head may have been deleted), and advancing prev even on the iteration where you unlinked.
2. Prev–Cur and Pointer-to-Pointer
Tell: in-place deletion, "delete the node" — especially when even the head can be removed with no special case.
Hold the address of the pointer that points at the current node. Unlinking becomes a single write and the head needs no special handling.
ListNode** pp = &head; // pp aims at the pointer to cur
while (*pp) {
if (shouldRemove(*pp)) {
ListNode* dead = *pp;
*pp = (*pp)->next; // splice out, head included
delete dead;
} else {
pp = &(*pp)->next; // advance ONLY when keeping
}
}
return head;
Solves: 203 Remove Elements, 83 Remove Duplicates, 237 Delete Node (the copy-forward variant).
Trap: advancing pp in both branches — after a delete, *pp already points at the next node, so advancing skips it.
3. Fast & Slow (Different Speeds)
Tell: "the middle", "is there a cycle", "without knowing the length", "in one pass".
Two pointers moving at different rates. When the fast one falls off the end, the slow one is at a structurally meaningful place.
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next) {
slow = slow->next; // 1 step
fast = fast->next->next; // 2 steps
}
// slow == middle (the second middle when the length is even)
Solves: 876 Middle of the List, 141 Cycle, 142 Cycle II, 234 Palindrome.
Trap: the loop condition decides which middle you land on. while (fast && fast->next) gives the second middle; starting fast = head->next gives the first. Pick deliberately.
4. Fixed-Gap Two Pointers
Tell: "n-th node from the end", "where two lists intersect", a fixed offset between two cursors.
Open a gap of exactly n between two pointers, then move both until the leader hits the end. The trailer is now n from the end.
ListNode dummy(0);
dummy.next = head;
ListNode* lead = &dummy;
for (int i = 0; i < n; ++i) lead = lead->next; // gap of n
ListNode* trail = &dummy;
while (lead->next) { lead = lead->next; trail = trail->next; }
// trail->next is the n-th node from the end (safe to delete)
Solves: 19 Remove n-th From End, 160 Intersection, 1721 Swapping Nodes.
Trap: off-by-one in the gap. Anchoring both pointers on a dummy makes removing the head fall out for free instead of needing its own branch.
5. Reverse a Segment
Tell: "reverse", "palindrome", "reorder", "in groups of k", "O(1) extra space".
The three-pointer save–rewire–advance loop. Every reordering problem is this loop applied to a chosen span.
ListNode* prev = nullptr;
ListNode* cur = head;
while (cur) {
ListNode* nx = cur->next; // save
cur->next = prev; // rewire
prev = cur; // advance
cur = nx;
}
return prev; // new head of the reversed span
Solves: 206 Reverse, 92 Reverse II, 25 K-Group, 143 Reorder.
Trap: for a sub-list you must remember the node before the segment and the segment's original head (its future tail) to reconnect — see Sublist & K-Group Reversal.
6. Build Two Lists and Stitch
Tell: "partition around x", "group odd and even positions", "separate by a predicate".
Walk once, appending each node to one of two sublists by a test, then join them. Two dummies keep the append O(1).
ListNode lo(0), hi(0);
ListNode* lt = &lo;
ListNode* ht = &hi;
for (ListNode* p = head; p; p = p->next) {
if (pred(p)) { lt->next = p; lt = p; }
else { ht->next = p; ht = p; }
}
ht->next = nullptr; // TERMINATE, or you build a cycle
lt->next = hi.next;
return lo.next;
Solves: 86 Partition, 328 Odd Even, 725 Split in Parts.
Trap: forgetting ht->next = nullptr. The last appended node keeps its old next and closes a loop — the accidental cycle from Pitfalls.
7. Merge / Divide and Conquer
Tell: "sorted", "merge two/k sorted lists", "sort a list".
The two-way merge with a dummy tail is the primitive; k-way and merge sort are it applied recursively.
ListNode dummy(0);
ListNode* tail = &dummy;
while (a && b) {
if (a->val <= b->val) { tail->next = a; a = a->next; }
else { tail->next = b; b = b->next; }
tail = tail->next;
}
tail->next = a ? a : b; // one list is empty; attach the remainder
return dummy.next;
Solves: 21 Merge Two, 23 Merge k, 148 Sort List, 147 Insertion Sort.
Trap: merging k lists by folding them in one at a time is O(kN). Use a heap (O(N log k)) or pairwise divide and conquer instead — see Merging.
8. Hash Map + List
Tell: "O(1) get and put", "least recently used", "clone with random pointers".
A hash map for O(1) lookup, a doubly linked list for O(1) reordering/eviction. The map stores iterators/node pointers into the list.
// LRU: unordered_map<int, list<pair<int,int>>::iterator> pos; list<pair<int,int>> items;
void put(int key, int val) {
if (pos.count(key)) items.erase(pos[key]); // drop the stale node
items.push_front({key, val}); // most-recent at front
pos[key] = items.begin();
if (items.size() > cap) { // evict the tail
pos.erase(items.back().first);
items.pop_back();
}
}
Solves: 146 LRU Cache, 460 LFU Cache, 138 Copy with Random Pointer, 355 Design Twitter.
Trap: forgetting to erase the stale map entry on eviction (a slow leak of keys), or reaching for a singly linked list — you cannot unlink a node from it in O(1).
The Decision Tree
Run the questions top to bottom and stop at the first "yes". The order matters: the cheaper, more constraining signals come first.
- Does it demand O(1) extra space while reversing or reordering? → Reverse a segment (206, 92, 25, 143, 234).
- Can the head be removed, or are you deleting many nodes? → Dummy head / pointer-to-pointer (203, 82, 19).
- Do you need the middle, a cycle, or the n-th from the end without the length? → Fast & slow or fixed-gap (876, 141, 142, 19, 160).
- Are the inputs sorted, or must you merge k? → Merge / divide & conquer (21, 23, 148).
- Do you need O(1) get and put by key? → Hash map + list (146, 460, 138).
- None of the above? → plain prev–cur pointer surgery at the node you found.
Step the animation to watch the walk for several real problems. Use Next Path to switch problems and see a different branch light up.
▶ From Statement to Pattern
Each step answers one question and highlights the branch taken, ending on the recommended pattern. Next Path loads a different example problem.
How to Actually Run the Round
A correct answer typed in silence scores worse than the same answer narrated well. The linked-list round tests communication and edge-case discipline as much as the algorithm. Do these five things in order.
Ask the five clarifying questions first
Before writing anything, resolve the ambiguities that change the solution:
- Singly or doubly linked? A
prevpointer turns O(n) neighbour-finding into O(1) and unlocks O(1) deletion given only a node. - May I modify the input list? If not, you need extra space; if yes, in-place reversal and reordering are on the table.
- Can there be a cycle? A cycle turns every naive traversal into an infinite loop; you may need Floyd first.
- Can values repeat? Decides whether you can key on value or must key on node identity.
- Is the list sorted? Sorted input often collapses an O(n log n) solution to O(n) with two pointers.
Narrate the pointer surgery out loud
Say the invariant, then the three steps, as you write them: "I keep prev, cur, next; I save next, point cur back at prev, then walk both forward." Interviewers grade the reasoning; a spoken invariant tells them you are not pattern-matching from memory but deriving it.
Draw the before/after, do not trace code
Sketch the four boxes and arrows and redraw only the pointers that change. A single "before / after" diagram of one splice communicates more than tracing ten lines of code line by line, and it catches your own off-by-one before you compile it in your head.
Test empty, one, and two nodes — every time
These three inputs break more submissions than any large case. Walk your code on the empty list (does the loop condition dereference null?), the single node (does fast->next exist?), and two nodes (does the middle/pair logic pick the right one?). Volunteer these before the interviewer asks.
State complexity unprompted
Close with time and extra space without being asked: "O(n) time, O(1) extra space, one pass." It signals you know the cost of what you wrote and pre-empts the obvious follow-up.
next before I overwrite cur->next." · "This is O(1) extra space because I reverse in place." · "Let me check the empty and single-node cases." · "Values can repeat, so I'll key on node identity, not value."
Complexity Table to Reproduce From Memory
You should be able to write this table on a whiteboard without hesitation. It is the twelve canonical operations and the pattern each one belongs to.
| Operation | Time | Extra space | Pattern |
|---|---|---|---|
| Reverse entire list | O(n) | O(1) | Reverse a segment |
| Find middle node | O(n) | O(1) | Fast & slow |
| Detect cycle + find entrance | O(n) | O(1) | Fast & slow |
| n-th node from the end | O(n) | O(1) | Fixed-gap |
| Remove n-th from the end | O(n) | O(1) | Fixed-gap + dummy |
| Merge two sorted lists | O(n + m) | O(1) | Merge |
| Merge k sorted lists | O(N log k) | O(k) | Merge + heap |
| Sort a list (merge sort) | O(n log n) | O(1) bottom-up | Merge / D&C |
| Partition around a value | O(n) | O(1) | Build two lists |
| Reorder L0→Ln→L1… | O(n) | O(1) | Fast&slow + reverse + merge |
| Palindrome check | O(n) | O(1) | Fast&slow + reverse |
| Clone with random pointers | O(n) | O(1) interleaved | Weave (or hash map) |
| LRU cache get / put | O(1) | O(capacity) | Hash map + list |
Check Yourself
You are given a problem statement. Name the pattern you would reach for.
Practice
- LeetCode 25 — Reverse Nodes in k-Group reverse the reverse-a-segment pattern with reconnection bookkeeping.
- LeetCode 19 — Remove Nth From End gap fixed-gap two pointers on a dummy so the head case is free.
- LeetCode 86 — Partition List two-lists build-two-and-stitch; remember to terminate.
- LeetCode 23 — Merge k Sorted Lists merge heap vs divide-and-conquer; state both complexities.
- LeetCode 146 — LRU Cache hash+list the map-plus-doubly-linked-list design end to end.
- LeetCode 143 — Reorder List combo three patterns in one: find middle, reverse, merge.