Pattern

Linked List Techniques

Master the dummy head, two-pointer, and partition tricks that turn linked list problems from tricky edge-case nightmares into clean, predictable code.

Roadmap

  1. What Is This Pattern?
  2. When to Use It
  3. Variant 1 — Dummy Head (Merge, Insert, Delete)
  4. Variant 2 — Partition and Rearrange
  5. Template Code
  6. Visual Walkthrough
  7. Common Problems
  8. Interactive Demo
  9. Complexity Analysis
  10. Key Takeaways
Step 01

What Is This Pattern?

Linked list problems revolve around pointer manipulation. Unlike arrays, you cannot index into position i in O(1). Instead, you traverse nodes one at a time, rewiring .next pointers to merge, split, reverse, or rearrange. The two most powerful techniques are the dummy head (a sentinel node that eliminates head-of-list edge cases) and partitioning with two tails (splitting nodes into separate chains and reconnecting them).

The Core Idea

A dummy head node sits before the real head. You build the result list by appending to a tail pointer. When you are done, dummy.next is the real head. No special-casing the first node.

Dummy head pattern
D
1
3
5
null
tail always points to the last node in the built-up result.
Append: tail.next = chosen; tail = tail.next;
At the end, return dummy.next to skip the sentinel.
💡

Why a dummy head? Without it, you must write separate logic for "is the result list empty?" versus "append to existing list." The dummy node guarantees the list always has at least one node, so tail.next = ... is always valid. This eliminates an entire class of null-pointer bugs.

Step 02

When to Use It

Look for these recognition signals in the problem statement. If you spot one, a dummy head or partition approach is very likely the cleanest solution.

"Merge two sorted lists"
Compare heads of two lists, pick the smaller, advance that pointer. Classic dummy head usage.
"Partition list around x"
Build two sub-lists (less-than and greater-or-equal), then stitch them together.
"Remove nodes" or "delete given value"
A dummy head lets you delete the real head without special cases. Traverse with prev and curr.
"Insert into sorted list" or "reorder nodes"
Use a dummy head to handle insertion before the current head, and tail pointers to rearrange in one pass.

The key clue: Whenever the problem says "return the head of the modified list" and the head itself might change (due to merging, deletion, or reordering), reach for a dummy head. It costs O(1) extra space and eliminates edge cases completely.

Step 03

Variant 1 — Dummy Head (Merge, Insert, Delete)

Merge Two Sorted Lists

Create a dummy node and a tail pointer. Compare the heads of both input lists. Whichever is smaller gets appended to tail, then advance that list's pointer. Repeat until one list is exhausted, then attach the remainder.

Walkthrough: Merge [1,3,5] and [2,4,6]

Result: 1 → 2 → 3 → 4 → 5 → 6

Compare: 1 vs 2
List A
1
3
5
List B
2
4
6
1 ≤ 2, pick from A. Append 1 to tail. Advance A.
1
Compare: 3 vs 2
List A
3
5
List B
2
4
6
2 < 3, pick from B. Append 2 to tail. Advance B.
1
2
Compare: 3 vs 4
List A
3
5
List B
4
6
3 ≤ 4, pick from A. Append 3 to tail. Advance A.
1
2
3
Compare: 5 vs 4
List A
5
List B
4
6
4 < 5, pick from B. Append 4 to tail. Advance B.
1
2
3
4
Compare: 5 vs 6
List A
5
List B
6
5 ≤ 6, pick from A. Append 5 to tail. A is now exhausted.
1
2
3
4
5
Done
A is empty. Attach the remainder of B (6) to tail. Return dummy.next.
Result:
1
2
3
4
5
6
1
2
3
4
5
6
Step 04

Variant 2 — Partition and Rearrange

Partition List

Create two dummy heads — one for nodes less than x, one for nodes ≥ x. Walk the original list, appending each node to the appropriate chain. Then connect the two chains and set the last node's next to null.

Example: Partition [1, 4, 3, 2, 5, 2] around x = 3

Nodes < 3 come first, then nodes ≥ 3, preserving original order within each group. Result: 1 → 2 → 2 → 4 → 3 → 5

node 1: 1 < 3, append to less chain. Less: [1]
node 4: 4 ≥ 3, append to greater chain. Greater: [4]
node 3: 3 ≥ 3, append to greater chain. Greater: [4, 3]
node 2: 2 < 3, append to less chain. Less: [1, 2]
node 5: 5 ≥ 3, append to greater chain. Greater: [4, 3, 5]
node 2: 2 < 3, append to less chain. Less: [1, 2, 2]
Stitch: less tail → greater head. Set greater tail.next = null.
1
2
2
4
3
5
null
🎯

Two dummies, one stitch: The partition technique generalizes to any "split then reconnect" problem. Both chains get their own dummy head, so you never need to check if a chain is empty before appending. At the end, connect lessTail.next = greaterDummy.next and greaterTail.next = null (to avoid cycles).

Step 05

Template Code

Here are the two annotated Java templates. The merge template uses one dummy; the partition template uses two.

Merge Two Sorted Lists

// Merge two sorted linked lists using a dummy head
ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0);   // sentinel
    ListNode tail = dummy;               // build result here

    while (l1 != null && l2 != null) {
        if (l1.val <= l2.val) {
            tail.next = l1;              // pick from l1
            l1 = l1.next;
        } else {
            tail.next = l2;              // pick from l2
            l2 = l2.next;
        }
        tail = tail.next;                // advance tail
    }
    tail.next = (l1 != null) ? l1 : l2;  // attach remainder

    return dummy.next;                   // skip sentinel
}

Partition List (two dummies)

// Partition list around value x
ListNode partition(ListNode head, int x) {
    ListNode lessD = new ListNode(0);   // dummy for < x
    ListNode greaterD = new ListNode(0); // dummy for ≥ x
    ListNode less = lessD, greater = greaterD;

    while (head != null) {
        if (head.val < x) {
            less.next = head;            // append to less chain
            less = less.next;
        } else {
            greater.next = head;         // append to greater chain
            greater = greater.next;
        }
        head = head.next;
    }
    greater.next = null;                 // terminate greater chain
    less.next = greaterD.next;           // stitch chains

    return lessD.next;
}
📝

Always terminate with null. When partitioning, the last node in the "greater" chain might still point to a node in the "less" chain from the original list. Setting greater.next = null before stitching prevents cycles and infinite loops.

Step 06

Visual Walkthrough

Let us trace through merging two sorted lists step by step, showing pointer positions and the growing result at every stage.

Merge: A = [1, 4, 7] and B = [2, 3, 8]

Building the merged list using a dummy head and tail pointer.

STEP ACTION A PTR B PTR RESULT
1 vs 2 Pick 1 from A [4, 7] [2, 3, 8] D → 1
4 vs 2 Pick 2 from B [4, 7] [3, 8] D → 1 → 2
4 vs 3 Pick 3 from B [4, 7] [8] D → 1 → 2 → 3
4 vs 8 Pick 4 from A [7] [8] D → 1 → 2 → 3 → 4
7 vs 8 Pick 7 from A [] [8] D → 1..4 → 7
Attach A empty, attach B remainder [] [] D → 1..7 → 8
1
2
3
4
7
8
💡

Notice the pattern: Each comparison moves one pointer forward and appends one node. With n + m total nodes, we do exactly n + m comparisons and pointer moves, giving O(n + m) time. The dummy head means zero special cases — the very first append and every subsequent append use the same code path.

Step 07

Common Problems

These are classic LeetCode problems that use linked list techniques, listed in rough order of difficulty.

#21 Merge Two Sorted Lists Easy
#160 Intersection of Two Linked Lists Easy
#2 Add Two Numbers Medium
#86 Partition List Medium
#138 Copy List with Random Pointer Medium
#23 Merge k Sorted Lists Hard
💡

Practice tip: Start with #21 (direct application of the merge template) and #86 (direct application of the partition template). Once comfortable, tackle #23 which extends merge to k lists using a min-heap or divide-and-conquer. #138 requires a creative interleaving trick or a hash map to handle random pointers during a deep copy.

Step 08

Interactive Demo

Enter two sorted linked lists and step through the merge algorithm. Watch the dummy head, pointer movement, and the merged result building up node by node.

⚙ Merge Two Sorted Lists Visualizer



LIST A
LIST B
MERGED RESULT
Press "Step →" or "Run All" to begin.
Step 09

Complexity Analysis

Time
O(n + m)
Space
O(1)

Why O(n + m) Time, O(1) Space?

The merge and partition algorithms visit each node exactly once. With n nodes in one list and m in the other, the total work is O(n + m). Each comparison yields one append, so there is no wasted work.

Space: We only create the dummy node(s) — a constant number of extra pointers. The nodes themselves are rewired in place, not copied. Total extra space: O(1).

Exception: Merge k Sorted Lists using a min-heap is O(N log k) time and O(k) space, where N is the total number of nodes and k is the number of lists. The heap holds at most k nodes at any time.

In-place rewiring: Unlike array-based merging which requires O(n + m) extra space for the output array, linked list merging rewires existing nodes. The dummy head is the only allocation. This is one of the rare cases where linked lists are genuinely more space-efficient than arrays.

Step 10

Key Takeaways

01

Dummy head eliminates edge cases. A sentinel node before the real head means you never need to check "is the list empty?" before appending. The first append and every subsequent append use the identical code path: tail.next = node; tail = tail.next.

02

Two dummies for partition problems. When you need to split a list into two groups (less/greater, odd/even, etc.), give each group its own dummy head. Append nodes to the appropriate chain, then stitch the chains together at the end.

03

Always terminate with null. After rearranging nodes, the last node in your result might still have a stale .next pointer from the original list. Set it to null to prevent cycles and infinite loops.

04

Return dummy.next, not dummy. The dummy is a sentinel that should not be part of the result. Always return dummy.next to skip it. This is a common interview mistake — returning the dummy gives a list with an extra leading zero.

05

Merge k lists with divide-and-conquer or a min-heap. The two-list merge is the building block. To merge k sorted lists, either pair them up and merge bottom-up (like merge sort, O(N log k) time) or use a min-heap to always extract the global minimum across all k list heads.