← All Posts
DSA · Linked Lists · Part 26 of 28

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.

  1. Does it demand O(1) extra space while reversing or reordering? → Reverse a segment (206, 92, 25, 143, 234).
  2. Can the head be removed, or are you deleting many nodes? → Dummy head / pointer-to-pointer (203, 82, 19).
  3. 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).
  4. Are the inputs sorted, or must you merge k? → Merge / divide & conquer (21, 23, 148).
  5. Do you need O(1) get and put by key? → Hash map + list (146, 460, 138).
  6. 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:

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.

Phrases that earn points: "Let me use a dummy head so removing the first node isn't a special case." · "I'll save 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.

OperationTimeExtra spacePattern
Reverse entire listO(n)O(1)Reverse a segment
Find middle nodeO(n)O(1)Fast & slow
Detect cycle + find entranceO(n)O(1)Fast & slow
n-th node from the endO(n)O(1)Fixed-gap
Remove n-th from the endO(n)O(1)Fixed-gap + dummy
Merge two sorted listsO(n + m)O(1)Merge
Merge k sorted listsO(N log k)O(k)Merge + heap
Sort a list (merge sort)O(n log n)O(1) bottom-upMerge / D&C
Partition around a valueO(n)O(1)Build two lists
Reorder L0→Ln→L1…O(n)O(1)Fast&slow + reverse + merge
Palindrome checkO(n)O(1)Fast&slow + reverse
Clone with random pointersO(n)O(1) interleavedWeave (or hash map)
LRU cache get / putO(1)O(capacity)Hash map + list

Check Yourself

You are given a problem statement. Name the pattern you would reach for.

Practice