โ† Back to DevBytes

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

Introduction to Rotate List

The "Rotate List" problem is a classic algorithmic challenge frequently encountered in coding interviews and competitive programming. At its core, the problem asks you to take a singly linked list and rotate it to the right by a given number of positions. While the concept sounds simple, implementing an efficient solution requires a solid understanding of linked list traversal, edge case handling, and pointer manipulation.

In this tutorial, we will walk through the problem step by step, explore multiple approaches, and discuss best practices to help you write clean, efficient, and interview-ready JavaScript code.

What Is the Rotate List Problem?

Given the head of a singly linked list and an integer k, rotate the list to the right by k places. Rotating to the right means that the last k nodes are moved to the front of the list.

Example

Consider the linked list: 1 -> 2 -> 3 -> 4 -> 5 and k = 2.

After rotating right by 2, the list becomes: 4 -> 5 -> 1 -> 2 -> 3.

Here is a breakdown of what happens:

Edge Cases to Consider

Why It Matters

The Rotate List problem is more than just an interview exercise. It tests several fundamental computer science concepts:

Mastering this problem builds a strong foundation for more complex data structure challenges involving trees, graphs, and doubly linked lists.

Setting Up the Linked List Structure

Before solving the problem, we need a basic linked list node definition in JavaScript. We will use the ES6 class syntax for clarity.

class ListNode {
  constructor(val, next = null) {
    this.val = val;
    this.next = next;
  }
}

// Helper function to build a linked list from an array
function buildList(arr) {
  if (arr.length === 0) return null;
  const head = new ListNode(arr[0]);
  let current = head;
  for (let i = 1; i < arr.length; i++) {
    current.next = new ListNode(arr[i]);
    current = current.next;
  }
  return head;
}

// Helper function to print a linked list
function printList(head) {
  const result = [];
  let current = head;
  while (current !== null) {
    result.push(current.val);
    current = current.next;
  }
  console.log(result.join(' -> '));
}

These helpers make it easier to test our solution with various inputs.

Approach 1: Naive Solution (Rotate One Step at a Time)

The most intuitive approach is to rotate the list one position at a time, repeating the process k times. Each rotation involves moving the last node to the front.

Implementation

function rotateRightNaive(head, k) {
  if (head === null || head.next === null || k === 0) {
    return head;
  }

  // First, find the length of the list
  let length = 1;
  let tail = head;
  while (tail.next !== null) {
    tail = tail.next;
    length++;
  }

  // Effective rotations needed
  k = k % length;
  if (k === 0) return head;

  for (let i = 0; i < k; i++) {
    // Find the new tail (second to last node)
    let newTail = head;
    while (newTail.next.next !== null) {
      newTail = newTail.next;
    }

    // The last node becomes the new head
    const newHead = newTail.next;
    newTail.next = null;
    newHead.next = head;
    head = newHead;
  }

  return head;
}

Complexity Analysis

While this approach works, it is not optimal for large inputs. Let us improve it.

Approach 2: Optimal Solution (Close the Loop)

The optimal approach takes advantage of the circular nature of the rotation. Instead of rotating one step at a time, we can:

This reduces the time complexity to O(n) with a single pass after computing the length.

Step-by-Step Implementation

function rotateRight(head, k) {
  // Edge case: empty list or single node or no rotation
  if (head === null || head.next === null || k === 0) {
    return head;
  }

  // Step 1: Compute the length of the list and find the tail
  let length = 1;
  let tail = head;
  while (tail.next !== null) {
    tail = tail.next;
    length++;
  }

  // Step 2: Compute effective rotations
  k = k % length;
  if (k === 0) return head;

  // Step 3: Close the loop to form a circular list
  tail.next = head;

  // Step 4: Find the new tail at position (length - k)
  let stepsToNewTail = length - k;
  let newTail = head;
  for (let i = 1; i < stepsToNewTail; i++) {
    newTail = newTail.next;
  }

  // Step 5: The new head is the node after the new tail
  const newHead = newTail.next;

  // Step 6: Break the circle
  newTail.next = null;

  return newHead;
}

How It Works

Let us trace through the example 1 -> 2 -> 3 -> 4 -> 5 with k = 2:

Complexity Analysis

Testing the Solution

Now let us test our optimal solution with several cases to ensure correctness.

// Test Case 1: Normal rotation
const list1 = buildList([1, 2, 3, 4, 5]);
const rotated1 = rotateRight(list1, 2);
printList(rotated1); // Output: 4 -> 5 -> 1 -> 2 -> 3

// Test Case 2: k is larger than the list length
const list2 = buildList([1, 2, 3]);
const rotated2 = rotateRight(list2, 5);
printList(rotated2); // Output: 2 -> 3 -> 1

// Test Case 3: k equals the list length
const list3 = buildList([1, 2, 3, 4]);
const rotated3 = rotateRight(list3, 4);
printList(rotated3); // Output: 1 -> 2 -> 3 -> 4

// Test Case 4: Empty list
const list4 = buildList([]);
const rotated4 = rotateRight(list4, 3);
printList(rotated4); // Output: (empty)

// Test Case 5: Single node
const list5 = buildList([42]);
const rotated5 = rotateRight(list5, 99);
printList(rotated5); // Output: 42

// Test Case 6: k is zero
const list6 = buildList([1, 2, 3]);
const rotated6 = rotateRight(list6, 0);
printList(rotated6); // Output: 1 -> 2 -> 3

All test cases should produce the expected outputs, confirming that our solution handles edge cases correctly.

Common Mistakes to Avoid

Best Practices

1. Always Handle Edge Cases First

Start your function with guard clauses for empty lists, single-node lists, and zero rotations. This makes the code more readable and prevents unnecessary computation.

if (head === null || head.next === null || k === 0) {
  return head;
}

2. Use the Modulo Operator

The modulo operator is essential for handling cases where k is larger than the list length. It ensures you only perform the minimum necessary rotations.

k = k % length;
if (k === 0) return head;

3. Write Helper Functions for Testing

Functions like buildList and printList make it much easier to test and debug your solution. They also improve code readability.

4. Use Descriptive Variable Names

Instead of using generic names like node or ptr, use descriptive names such as tail, newTail, and newHead. This makes the logic self-documenting.

5. Test with Multiple Inputs

Always test your solution with a variety of inputs, including edge cases. This builds confidence that your code is robust and production-ready.

6. Avoid Mutating the Input Unnecessarily

In some scenarios, you may want to preserve the original list. If immutability is a requirement, consider creating a deep copy before performing the rotation.

Variations of the Problem

Once you understand the basic rotation, you can explore related challenges:

Left Rotation Implementation

As a bonus, here is how you can implement a left rotation using the same circular list technique.

function rotateLeft(head, k) {
  if (head === null || head.next === null || k === 0) {
    return head;
  }

  // Compute length and find tail
  let length = 1;
  let tail = head;
  while (tail.next !== null) {
    tail = tail.next;
    length++;
  }

  k = k % length;
  if (k === 0) return head;

  // Close the loop
  tail.next = head;

  // For left rotation, the new tail is at position k
  let newTail = head;
  for (let i = 1; i < k; i++) {
    newTail = newTail.next;
  }

  const newHead = newTail.next;
  newTail.next = null;

  return newHead;
}

// Test: 1 -> 2 -> 3 -> 4 -> 5, k = 2
// Expected output: 3 -> 4 -> 5 -> 1 -> 2
const leftList = buildList([1, 2, 3, 4, 5]);
printList(rotateLeft(leftList, 2));

Conclusion

The Rotate List problem is an excellent exercise for strengthening your understanding of linked list manipulation and algorithmic thinking. By moving from a naive one-step-at-a-time approach to the optimal circular list technique, you learn how to identify inefficiencies and apply clever insights to reduce time complexity. Remember to always handle edge cases, use the modulo operator to normalize k, and test your solution thoroughly with diverse inputs. With these techniques in your toolkit, you will be well-prepared to tackle this problem and similar linked list challenges in both interviews and real-world development scenarios.

๐Ÿ›  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