โ† Back to DevBytes

Solving Reorder List in JavaScript: Step-by-Step Guide

Solving Reorder List in JavaScript: Step-by-Step Guide

The Reorder List problem is a classic linked list challenge that frequently appears in coding interviews and algorithm practice. It tests your understanding of pointer manipulation, list traversal, and in-place transformations. In this tutorial, we'll break down the problem, understand the optimal approach, and implement a clean JavaScript solution step by step.

What Is the Reorder List Problem?

Given a singly linked list L: L0 โ†’ L1 โ†’ L2 โ†’ โ€ฆ โ†’ Ln-1 โ†’ Ln, you must reorder it in-place to:

L0 โ†’ Ln โ†’ L1 โ†’ Ln-1 โ†’ L2 โ†’ Ln-2 โ†’ โ€ฆ

You may not modify the values in the list's nodes โ€” only the next pointers themselves can be changed. This constraint forces you to manipulate the structure rather than simply copying values into an array and rebuilding the list.

For example, given the list 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5, the reordered result should be 1 โ†’ 5 โ†’ 2 โ†’ 4 โ†’ 3.

Why It Matters

This problem is valuable for several reasons:

The Strategy: Three Phases

The naive approach โ€” storing all nodes in an array and reconnecting them โ€” uses O(n) extra space. The optimal approach uses O(1) space by splitting the work into three phases:

Let's define our linked list node structure first:

function ListNode(val, next) {
  this.val = (val === undefined ? 0 : val);
  this.next = (next === undefined ? null : next);
}

Phase 1: Finding the Middle

We use two pointers: slow moves one step at a time, while fast moves two steps. When fast reaches the end, slow will be at the middle. To ensure a clean split for both even and odd length lists, we stop fast when fast.next or fast.next.next is null.

function findMiddle(head) {
  let slow = head;
  let fast = head;
  while (fast.next && fast.next.next) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}

For a list 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5, slow ends at node 3. The second half starts at slow.next, which is 4.

Phase 2: Reversing the Second Half

Reversing a singly linked list is a foundational skill. We iterate through the list, flipping each next pointer to point backward.

function reverseList(head) {
  let prev = null;
  let curr = head;
  while (curr) {
    const nextNode = curr.next;
    curr.next = prev;
    prev = curr;
    curr = nextNode;
  }
  return prev;
}

After reversing 4 โ†’ 5, we get 5 โ†’ 4, with 5 as the new head of the reversed half.

Phase 3: Merging the Two Halves

Now we interleave nodes from the first half (1 โ†’ 2 โ†’ 3) with the reversed second half (5 โ†’ 4). We take one node from each list alternately, reconnecting next pointers as we go.

function mergeLists(l1, l2) {
  while (l2) {
    const l1Next = l1.next;
    const l2Next = l2.next;
    l1.next = l2;
    l2.next = l1Next;
    l1 = l1Next;
    l2 = l2Next;
  }
}

We loop while l2 exists because the second half is always shorter than or equal to the first half, so it determines when merging is complete.

Putting It All Together

Here is the complete reorderList function that orchestrates all three phases:

function reorderList(head) {
  if (!head || !head.next) return head;

  // Phase 1: Find the middle
  let slow = head;
  let fast = head;
  while (fast.next && fast.next.next) {
    slow = slow.next;
    fast = fast.next.next;
  }

  // Phase 2: Reverse the second half
  let secondHalf = slow.next;
  slow.next = null; // Cut the list in two
  let prev = null;
  let curr = secondHalf;
  while (curr) {
    const nextNode = curr.next;
    curr.next = prev;
    prev = curr;
    curr = nextNode;
  }
  secondHalf = prev;

  // Phase 3: Merge the two halves
  let first = head;
  let second = secondHalf;
  while (second) {
    const firstNext = first.next;
    const secondNext = second.next;
    first.next = second;
    second.next = firstNext;
    first = firstNext;
    second = secondNext;
  }

  return head;
}

Testing the Solution

Let's build a helper to create a list from an array and another to print it, then verify our solution:

function arrayToList(arr) {
  let dummy = new ListNode();
  let curr = dummy;
  for (const val of arr) {
    curr.next = new ListNode(val);
    curr = curr.next;
  }
  return dummy.next;
}

function listToArray(head) {
  const result = [];
  while (head) {
    result.push(head.val);
    head = head.next;
  }
  return result;
}

// Test cases
const list1 = arrayToList([1, 2, 3, 4, 5]);
reorderList(list1);
console.log(listToArray(list1)); // [1, 5, 2, 4, 3]

const list2 = arrayToList([1, 2, 3, 4]);
reorderList(list2);
console.log(listToArray(list2)); // [1, 4, 2, 3]

const list3 = arrayToList([1]);
reorderList(list3);
console.log(listToArray(list3)); // [1]

Complexity Analysis

Best Practices and Common Pitfalls

Variations to Practice

Once you're comfortable with the basic reorder, try these related challenges to deepen your understanding:

Conclusion

The Reorder List problem is a perfect exercise in decomposing a complex transformation into smaller, well-understood operations. By combining middle-finding, list reversal, and interleaved merging, you arrive at an elegant O(n) time and O(1) space solution that respects the in-place constraint. Mastering this pattern not only prepares you for interviews but also builds the pointer-manipulation intuition you'll need for more advanced data structure problems. Practice each phase in isolation until it feels automatic, then assemble them confidently whenever this problem โ€” or a variation of it โ€” appears.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles