Pattern 9 of 24 · Linked lists · 30 min

In-place Linked List Manipulation

Reverse, merge and splice lists by rewiring next pointers with prev/curr/next, using a dummy head to remove edge cases.

Spot it:reverse a list or part of itmerge sorted listsremove the nth nodereorder nodesJavaScript you'll use: Linked list and tree nodes Destructuring, spread and rest
Say it in 30 seconds
  • O(n)Time: each node is rewired at most once
  • O(1)Extra space; copying to an array is O(n)
  • 3 pointersprev, curr and next for an in-place reversal
Show what to say

“To reverse, merge, reorder or remove nodes in place, I rewire next pointers instead of copying values: save curr.next, flip, advance. I put a dummy node in front when the head can change, and fix the two end links after a splice. It is O(n) time and O(1) space.”

Trade-offs

  • Iterative vs recursive reversal: same O(n) time, but recursion holds one frame per node and can overflow on lists of tens of thousands.
  • Dummy head vs special-casing the head: one extra node gives the head a predecessor, so edits need fewer branches and fewer bugs.
  • Reverse the back half vs copy values to an array: O(1) against O(n) extra space, but cut first or you create a cycle.

Bring it up before they ask

Save curr.next before you overwrite it, return prev or dummy.next rather than the old head, and test lists of length 0, 1 and 2.

0 of 0 sections ticked
30-second answer

Linked list problems are solved by rewiring next pointers. Save curr.next before you overwrite it, and put a dummy node in front when the head can change. O(n) time, O(1) space.

When to reach for it

Spot it in the problem0 of 4

Tap the signals you can see in the problem.

RememberReverseMergereorderremoveback read backwards

Pick your variant
  • Reverse in place

    Pointersprev, curr, next

    Use it whenevery arrow turns around

  • Dummy head

    Pointersdummy, plus tail or prev

    Use it whenthe head may change

  • Reverse a sublist and splice

    Pointersbefore, first, plus the trio

    Use it whenonly part of the list flips

  • Split, reverse, weave

    Pointersslow/fast, the trio, a/b

    Use it whenthe back half is needed backwards

  • Fixed gap

    Pointersfast n + 1 ahead of slow

    Use it whenyou count from the end

Play with it

Press the four assignments in order to reverse the train. Then flip before you save and watch the rest derail.

Read more · 58 words
  1. Draw before and after

    Circle every arrow that changes.

  2. Name a pointer for every node you touch

    prev, curr, next.

  3. Order the assignments so nothing is lost

    Save x.next before you overwrite it.

  4. Put a dummy in front when the head can change

    Return dummy.next, not head.

  5. Test the tiny lists

    Empty, one node, two nodes.

Interactive

Flip the train

Couplings are next pointers. Press the four moves in order, or cars derail.

∅12345▲ prev▲ curr
Moves 0Couplings flipped 0/5Extra memory 3 pointers (an array copy: 5 slots)

Each car points at the next one. Press the moves in the right order to reverse the train, or Step to watch the textbook move.

Your train

Templates

Template 1: reverse a whole list

This is Reverse Linked ListEasy, the loop inside every template below.

// Template 1: reverse a list in place. prev heads the part that is already reversed; curr is the next node to flip.
// Save curr.next BEFORE overwriting it, or the rest of the list is lost.
export function reverseList(head) {
  let prev = null;
  let curr = head;
  while (curr) {
    const next = curr.next; // 1. save the rest
    curr.next = prev;       // 2. flip the arrow
    prev = curr;            // 3. move both pointers forward
    curr = next;
  }
  return prev;              // the old tail is the new head
}

Template 2: a dummy head

If the head can go, what you return changes.

Read more · 52 words

A dummy node in front gives the head a predecessor, so there is no special case. Stop one node before the target and return dummy.next.

mergeSorted is Merge Two Sorted ListsEasy. For Remove Nth Node From End of ListMedium, start fast n + 1 steps ahead, then move both until fast is null: slow stops just before the node to remove.

import { ListNode } from './list.js';

// Template 2: a dummy node in front of the head, so the head can be removed or replaced like any other node.
export function removeAll(head, value) {
  const dummy = new ListNode(0, head);
  let prev = dummy;
  while (prev.next) {
    if (prev.next.val === value) prev.next = prev.next.next; // unlink; don't move: the new prev.next is unchecked
    else prev = prev.next;
  }
  return dummy.next; // may not be the old head any more
}

// Building a list: with a dummy and a tail pointer, appending never needs a special case for the first node.
export function mergeSorted(a, b) {
  const dummy = new ListNode();
  let 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 ?? b; // the rest of the other list is already sorted
  return dummy.next;
}

Template 3: reverse a sublist and splice it back

Stand on before, the node in front of the part, and reverse k nodes.

Read more · 26 words

Two links are now wrong. Fix them: before.next = prev and first.next = curr:

Repeat it for Reverse Linked List IIMedium (one range) or Reverse Nodes in k-GroupHard (while k nodes remain).

Reverse a sublist and splice it back

list
D123456▲ before▲ first
k
3
first
3

D is a dummy node in front of the head. before is node 2, found by walking 2 steps from D. first = before.next = 3: after the reversal it will be the segment's last node.

function reverseAfter(before, k) {
  const first = before.next;
  let prev = null, curr = first;
  for (let i = 0; i < k; i++) {
    const next = curr.next;
    curr.next = prev;
    prev = curr; curr = next;
  }
  before.next = prev;
  first.next = curr;
  return first;
}
All steps as text
  1. D is a dummy node in front of the head. before is node 2, found by walking 2 steps from D. first = before.next = 3: after the reversal it will be the segment's last node.
  2. Save next = 4 before flipping 3.
  3. Point 3 at null for now. It is the segment's future tail; the last splice step will connect it to the rest of the list.
  4. Move prev to 3 and curr to 4. 1 of 3 nodes reversed.
  5. Save next = 5 before flipping 4.
  6. Point 4 back at 3.
  7. Move prev to 4 and curr to 5. 2 of 3 nodes reversed.
  8. Save next = 6 before flipping 5.
  9. Point 5 back at 4.
  10. Move prev to 5 and curr to 6. 3 of 3 nodes reversed.
  11. The segment now reads 5 → 4 → 3, but before still pointed at 3, its old first node. Set before.next = prev, so 2 → 5.
  12. 3, now the segment's last node, still points at null. Set first.next = curr (6) to reattach the rest of the list.
  13. Return first (3): it is the node in front of whatever comes next, so the same call can reverse the next group. The list from D.next reads 1 → 2 → 5 → 4 → 3 → 6.
import { ListNode } from './list.js';

// Template 3: reverse the k nodes after `before` (they must exist) and splice them back into place.
// Returns the segment's new last node, which is the `before` of whatever comes next.
export function reverseAfter(before, k) {
  const first = before.next; // will become the segment's last node
  let prev = null;
  let curr = first;
  for (let i = 0; i < k; i++) {
    const next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }
  before.next = prev; // before → the segment's new first node
  first.next = curr;  // the segment's new last node → the rest of the list
  return first;
}

// Example: swap every two adjacent nodes, which is "reverse in groups of 2".
export function swapPairs(head) {
  const dummy = new ListNode(0, head);
  let before = dummy;
  while (before.next && before.next.next) before = reverseAfter(before, 2);
  return dummy.next;
}

Template 4: split, reverse, weave

A list can't walk backwards, so reverse the back half:

Read more · 44 words
  1. Find the middle with fast and slow pointers.
  2. Cut there (slow.next = null) and reverse the back half.
  3. Walk both halves together: compare or weave.

Save both successors, then link a → b → aNext:

fold is Reorder ListMedium. Compare instead of weaving to check a Palindrome Linked ListEasy.

Weave the front half with the reversed back half

list
162534▲ a▲ b
a
1
b
6

The list 1 → … → 6 was cut after its middle (3) and the back half reversed: a = 1 → 2 → 3 and b = 6 → 5 → 4. Nodes are drawn in their final order, so the curved arrows are the ones still to fix.

function weave(a, b) {
  const head = a;
  while (a && b) {
    const aNext = a.next, bNext = b.next;
    a.next = b;
    b.next = aNext ?? bNext;
    a = aNext; b = bNext;
  }
  return head;
}
All steps as text
  1. The list 1 → … → 6 was cut after its middle (3) and the back half reversed: a = 1 → 2 → 3 and b = 6 → 5 → 4. Nodes are drawn in their final order, so the curved arrows are the ones still to fix.
  2. Save both successors first: aNext = 2 and bNext = 5. The next two lines overwrite a.next and b.next.
  3. a.next = b: 1 now points at 6.
  4. b.next = aNext: 6 points at 2, so the front half continues after it.
  5. Move a to 2 and b to 5.
  6. Save both successors first: aNext = 3 and bNext = 4. The next two lines overwrite a.next and b.next.
  7. a.next = b: 2 now points at 5.
  8. b.next = aNext: 5 points at 3, so the front half continues after it.
  9. Move a to 3 and b to 4.
  10. Save both successors first: aNext = null and bNext = null. The next two lines overwrite a.next and b.next.
  11. a.next = b: 3 now points at 4.
  12. aNext is null, so b keeps its own rest: 4 points at null.
  13. Both halves are used up.
  14. Every arrow is straight: the list from the head reads 1 → 6 → 2 → 5 → 3 → 4, first, last, second, second-to-last, and so on. No node was created or copied.
import { reverseList } from './reverse.js';

// Template 4: interleave two lists in place, a1 → b1 → a2 → b2 → …
// Save BOTH successors before rewiring; whichever list is longer keeps its leftover nodes at the end.
export function weave(a, b) {
  const head = a;
  while (a && b) {
    const aNext = a.next;
    const bNext = b.next;
    a.next = b;
    b.next = aNext ?? bNext; // if a has run out, b keeps the rest of its own list
    a = aNext;
    b = bNext;
  }
  return head;
}

// The combo: L0 → Ln → L1 → Ln-1 → … in place. Find the middle, cut, reverse the back half, weave.
export function fold(head) {
  if (!head) return head;
  let slow = head;
  let fast = head;
  while (fast.next && fast.next.next) { // slow ends on the last node of the front half
    slow = slow.next;
    fast = fast.next.next;
  }
  const back = reverseList(slow.next);
  slow.next = null; // cut, or the front half still runs into the back half
  return weave(head, back);
}

Try it

try-it.js
// Edit the inputs and press Run. fromArray builds a list and toArray reads it back.
console.log(toArray(reverseList(fromArray([1, 2, 3, 4, 5]))));                  // [5, 4, 3, 2, 1]
console.log(toArray(removeAll(fromArray([6, 1, 6, 6, 2, 6]), 6)));              // [1, 2]
console.log(toArray(mergeSorted(fromArray([1, 4, 9]), fromArray([2, 3, 10])))); // [1, 2, 3, 4, 9, 10]

const dummy = new ListNode(0, fromArray([1, 2, 3, 4, 5, 6]));
reverseAfter(dummy.next.next, 3);                                               // the 3 nodes after node 2
console.log(toArray(dummy.next));                                               // [1, 2, 5, 4, 3, 6]
console.log(toArray(swapPairs(fromArray([1, 2, 3, 4, 5]))));                    // [2, 1, 4, 3, 5]
console.log(toArray(fold(fromArray([1, 2, 3, 4, 5, 6]))));                      // [1, 6, 2, 5, 3, 4]

Complexity and JavaScript you'll use

Complexity

Read more · 114 words
TechniqueTimeExtra space
Reverse in placeO(n)O(1) (recursive: O(n) call stack)
Dummy head: merge, removeO(n + m) / O(n)O(1): one dummy node
Reverse a sublist, or every group of kO(n): each node reversed at most onceO(1)
Split, reverse, weaveO(n): three linear passesO(1)
Copy values into an array insteadO(n)O(n)

JavaScript you'll use

JavaScript refresher: Linked list and tree nodes Destructuring, spread and rest

The right side is evaluated first, but order on the left still matters.

const dummy = new ListNode(0, head);           // a node in front of the head
const next = curr.next;                         // save before overwriting
curr.next = prev;                               // flip
[curr.next, prev, curr] = [prev, curr, curr.next]; // all three steps at once
return dummy.next;                              // the real head, even if it changed

Interview questions

Iterative or recursive reversal?

Iterative: O(1) space. Recursion can overflow on long lists.

Why use a dummy node?

The head gets a predecessor, so editing it needs no special case.

How do you rotate a list right by k?

Join the tail to the head, walk n - k % n - 1 steps to the new tail, and cut.

Add two numbers stored as lists, most significant digit first?

Reverse both, add with a carry, reverse the result. Or use two stacks.

Reverse in k-groups: how do you leave a short last group alone?

Walk k steps from before first. Stop if you hit null.

Check yourself

Question 1 of 3

Score 2 out of 3 or better to mark this page complete.

1. Inside the reversal loop, which statement must run first?

Practice problems

Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.

Open these in the problem list →

In real systems

This pattern is not only for coding rounds. Here is where it does the work in a design interview.

Next: Trees are linked lists with two next pointers. Depth-first search walks them recursively.