🎯 JavaScript Linked List β€” Top 25 Interview Questions


Q1 β€” Reverse Linked List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Pointer reversal β€” track prev, curr, next simultaneously.

Step 2 β€” Brute force (what NOT to do): Push to array, rebuild β€” O(n) space.

Step 3 β€” Optimal insight: Flip curr.next = prev iteratively.

Step 4 β€” Algorithm:

1. prev = null, curr = head

2. Each step: save next, flip pointer, advance both

3. Return prev

Step 5 β€” Edge cases: Empty list or one node β†’ return as-is.

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

Time: O(n) | Space: O(1)


Q2 β€” Find Middle of Linked List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Slow/fast pointer β€” fast moves 2x.

Step 2 β€” Brute force (what NOT to do): Count length, walk n/2 β€” two passes.

Step 3 β€” Optimal insight: One pass β€” when fast ends, slow is at middle.

Step 4 β€” Algorithm:

1. slow = fast = head

2. While fast && fast.next: slow++, fast += 2

3. Return slow

Step 5 β€” Edge cases: Even length β†’ returns second middle (LeetCode 876).

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

Time: O(n) | Space: O(1)


Q3 β€” Detect Cycle

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Floyd's cycle detection β€” fast laps slow.

Step 2 β€” Brute force (what NOT to do): Store every node in a Set β€” O(n) space.

Step 3 β€” Optimal insight: slow+1, fast+2. They must meet inside cycle.

Step 4 β€” Algorithm:

1. slow = fast = head

2. Advance both; strict equality check

3. Loop exits naturally β†’ no cycle

Step 5 β€” Edge cases: Null or 1 node β†’ false.

function hasCycle(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
}

Time: O(n) | Space: O(1)


Q4 β€” Find Cycle Start

🧠 How to Approach This

Step 1 β€” Recognize the pattern: After meeting point, reset slow to head and walk both at speed 1.

Step 2 β€” Brute force (what NOT to do): Track all visited nodes in a Set.

Step 3 — Optimal insight: Distance head→cycle_start = distance meeting_point→cycle_start.

Step 4 β€” Algorithm:

1. Detect meeting point (Floyd's)

2. Reset slow = head

3. Walk both at speed 1 β†’ meet at cycle start

Step 5 β€” Edge cases: No cycle β†’ return null.

function detectCycle(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      slow = head;
      while (slow !== fast) { slow = slow.next; fast = fast.next; }
      return slow;
    }
  }
  return null;
}

Time: O(n) | Space: O(1)


Q5 β€” Merge Two Sorted Lists

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Two-pointer merge step.

Step 2 β€” Brute force (what NOT to do): Collect values, sort, rebuild β€” O(n log n).

Step 3 β€” Optimal insight: Compare heads, attach smaller, advance β€” O(n+m).

Step 4 β€” Algorithm:

1. Dummy head, tail pointer

2. While both non-null: attach smaller, advance

3. Attach remaining tail

Step 5 β€” Edge cases: Either list empty.

function mergeTwoLists(l1, l2) {
  const dummy = new ListNode(0);
  let tail = dummy;
  while (l1 && l2) {
    if (l1.val <= l2.val) { tail.next = l1; l1 = l1.next; }
    else                  { tail.next = l2; l2 = l2.next; }
    tail = tail.next;
  }
  tail.next = l1 ?? l2;
  return dummy.next;
}

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


Q6 β€” Remove Nth Node From End

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Two pointers with N+1 gap.

Step 2 β€” Brute force (what NOT to do): Count then walk to position β€” two passes.

Step 3 β€” Optimal insight: Gap pointer, one pass.

Step 4 β€” Algorithm:

1. Dummy, fast advances N+1 steps

2. Move both until fast = null

3. Skip slow.next

Step 5 β€” Edge cases: Remove head β€” dummy handles.

function removeNthFromEnd(head, n) {
  const dummy = new ListNode(0, head);
  let slow = dummy, fast = dummy;
  for (let i = 0; i <= n; i++) fast = fast.next;
  while (fast) { slow = slow.next; fast = fast.next; }
  slow.next = slow.next.next;
  return dummy.next;
}

Time: O(n) | Space: O(1)


Q7 β€” Delete Node (No Head Given)

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Copy next node's value, skip next.

Step 2 β€” Brute force (what NOT to do): Traverse from head β€” but head isn't given.

Step 3 β€” Optimal insight: node.val = node.next.val; node.next = node.next.next.

Step 4 β€” Algorithm:

1. Copy next value into current node

2. Skip next node

Step 5 β€” Edge cases: Guaranteed not tail (per LeetCode 237).

function deleteNode(node) {
  node.val  = node.next.val;
  node.next = node.next.next;
}

Time: O(1) | Space: O(1)


Q8 β€” Intersection of Two Linked Lists

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Path equalisation β€” traverse both lists crosswise.

Step 2 β€” Brute force (what NOT to do): Store all A nodes in a Set, traverse B.

Step 3 — Optimal insight: a walks A→B, b walks B→A. Same total distance → meet at intersection.

Step 4 β€” Algorithm:

1. a = headA, b = headB

2. Advance; switch heads at null

3. Return when a === b

Step 5 β€” Edge cases: No intersection β†’ both reach null.

function getIntersectionNode(headA, headB) {
  let a = headA, b = headB;
  while (a !== b) {
    a = a !== null ? a.next : headB;
    b = b !== null ? b.next : headA;
  }
  return a;
}

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


Q9 β€” Palindrome Linked List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Find middle, reverse second half, compare halves.

Step 2 β€” Brute force (what NOT to do): Convert to array β€” O(n) space.

Step 3 β€” Optimal insight: Reverse in-place, compare β€” O(1) extra space.

Step 4 β€” Algorithm:

1. Find middle (slow/fast)

2. Reverse from middle onward

3. Walk both halves, compare

Step 5 β€” Edge cases: Odd vs even length.

function isPalindrome(head) {
  let slow = head, fast = head;
  while (fast && fast.next) { slow = slow.next; fast = fast.next.next; }
  let prev = null, curr = slow;
  while (curr) { const next = curr.next; curr.next = prev; prev = curr; curr = next; }
  let left = head, right = prev;
  while (right) {
    if (left.val !== right.val) return false;
    left = left.next; right = right.next;
  }
  return true;
}

Time: O(n) | Space: O(1)


Q10 β€” Remove Duplicates from Sorted List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Sorted = duplicates adjacent β†’ skip while equal.

Step 2 β€” Brute force (what NOT to do): Use a Set for seen values β€” O(n) space.

Step 3 β€” Optimal insight: Single traversal, skip equal next nodes.

Step 4 β€” Algorithm:

1. curr = head

2. While curr && curr.next: if equal skip next, else advance

Step 5 β€” Edge cases: All same, single node.

function deleteDuplicates(head) {
  let curr = head;
  while (curr && curr.next) {
    if (curr.val === curr.next.val) curr.next = curr.next.next;
    else curr = curr.next;
  }
  return head;
}

Time: O(n) | Space: O(1)


Q11 β€” Rotate List by K

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Form a ring, find new tail.

Step 2 β€” Brute force (what NOT to do): Rotate one step at a time β€” O(nΒ·k).

Step 3 β€” Optimal insight: Connect tail to head (ring), cut at new tail position.

Step 4 β€” Algorithm:

1. Get length, connect tail to head

2. k = k % len; new tail at len-k-1 from head

3. New head = newTail.next; newTail.next = null

Step 5 β€” Edge cases: k = 0 or k multiple of len.

function rotateRight(head, k) {
  if (!head || !head.next || k === 0) return head;
  let len = 1, tail = head;
  while (tail.next) { tail = tail.next; len++; }
  tail.next = head; // ring
  k = k % len;
  let stepsToNewTail = len - k - 1;
  let newTail = head;
  for (let i = 0; i < stepsToNewTail; i++) newTail = newTail.next;
  const newHead = newTail.next;
  newTail.next = null;
  return newHead;
}

Time: O(n) | Space: O(1)


Q12 β€” Add Two Numbers (Lists)

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Digit-by-digit addition, numbers stored in reverse.

Step 2 β€” Brute force (what NOT to do): Convert to BigInt, add, convert back β€” avoids overflow but obscures the pattern.

Step 3 β€” Optimal insight: Simulate school addition with carry.

Step 4 β€” Algorithm:

1. carry = 0, dummy head

2. While either list or carry: sum = l1.val + l2.val + carry; append sum%10; carry = Math.floor(sum/10)

Step 5 β€” Edge cases: Different length lists; leftover carry.

function addTwoNumbers(l1, l2) {
  const dummy = new ListNode(0);
  let curr = dummy, carry = 0;
  while (l1 || l2 || carry) {
    const sum = (l1?.val ?? 0) + (l2?.val ?? 0) + carry;
    carry = Math.floor(sum / 10);
    curr.next = new ListNode(sum % 10);
    curr = curr.next;
    l1 = l1?.next ?? null;
    l2 = l2?.next ?? null;
  }
  return dummy.next;
}

Time: O(max(n,m)) | Space: O(max(n,m))


Q13 β€” Copy List with Random Pointer

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Deep copy with arbitrary cross-links.

Step 2 β€” Brute force (what NOT to do): HashMap of original β†’ clone nodes β€” O(n) space.

Step 3 β€” Optimal insight: Interleave clones into original list, set randoms via interleaved positions, separate.

Step 4 β€” Algorithm:

1. Insert clone after each original node

2. Set clone.random = original.random.next

3. Separate the two lists

Step 5 β€” Edge cases: random = null; single node.

function copyRandomList(head) {
  if (!head) return null;
  // Step 1: interleave
  let curr = head;
  while (curr) {
    const clone = new Node(curr.val);
    clone.next = curr.next;
    curr.next  = clone;
    curr = clone.next;
  }
  // Step 2: set randoms
  curr = head;
  while (curr) {
    if (curr.random) curr.next.random = curr.random.next;
    curr = curr.next.next;
  }
  // Step 3: separate
  const dummy = new Node(0);
  let cloneTail = dummy; curr = head;
  while (curr) {
    cloneTail.next = curr.next;
    curr.next      = curr.next.next;
    cloneTail      = cloneTail.next;
    curr           = curr.next;
  }
  return dummy.next;
}

Time: O(n) | Space: O(1) extra (excluding output)


Q14 β€” Sort Linked List (Merge Sort)

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Merge sort β€” ideal for linked lists (no index needed).

Step 2 β€” Brute force (what NOT to do): Convert to array, sort, rebuild β€” O(n) space.

Step 3 β€” Optimal insight: Find middle, split, recurse, merge β€” O(n log n) time.

Step 4 β€” Algorithm:

1. Base case: null or single node

2. Find middle, split

3. Recursively sort, then merge

Step 5 β€” Edge cases: Empty, single node.

function sortList(head) {
  if (!head || !head.next) return head;
  let slow = head, fast = head.next;
  while (fast && fast.next) { slow = slow.next; fast = fast.next.next; }
  const mid = slow.next;
  slow.next = null;
  const left  = sortList(head);
  const right = sortList(mid);
  return mergeSorted(left, right);
}

function mergeSorted(l1, l2) {
  const dummy = new ListNode(0); let tail = dummy;
  while (l1 && l2) {
    if (l1.val <= l2.val) { tail.next = l1; l1 = l1.next; }
    else                  { tail.next = l2; l2 = l2.next; }
    tail = tail.next;
  }
  tail.next = l1 ?? l2;
  return dummy.next;
}

Time: O(n log n) | Space: O(log n) stack


Q15 β€” Odd Even Linked List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Separate odd-index nodes and even-index nodes, then concatenate.

Step 2 β€” Brute force (what NOT to do): Create new list β€” O(n) space.

Step 3 β€” Optimal insight: Two running pointers that leap-frog each other.

Step 4 β€” Algorithm:

1. odd = head, even = head.next, evenHead = even

2. While even && even.next: odd.next = even.next; odd = odd.next; even.next = odd.next; even = even.next

3. odd.next = evenHead

Step 5 β€” Edge cases: Less than 3 nodes.

function oddEvenList(head) {
  if (!head) return null;
  let odd = head, even = head.next, evenHead = even;
  while (even && even.next) {
    odd.next  = even.next;
    odd       = odd.next;
    even.next = odd.next;
    even      = even.next;
  }
  odd.next = evenHead;
  return head;
}

Time: O(n) | Space: O(1)


Q16 β€” Swap Nodes in Pairs

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Process two nodes at a time iteratively.

Step 2 β€” Brute force (what NOT to do): Collect values, swap in pairs, rebuild.

Step 3 β€” Optimal insight: prev dummy pointer, swap curr and curr.next in-place.

Step 4 β€” Algorithm:

1. dummy, prev = dummy

2. While 2 nodes: swap, advance prev by 2

Step 5 β€” Edge cases: Odd length β€” last node stays.

function swapPairs(head) {
  const dummy = new ListNode(0, head);
  let prev = dummy;
  while (prev.next && prev.next.next) {
    const first  = prev.next;
    const second = prev.next.next;
    first.next   = second.next;
    second.next  = first;
    prev.next    = second;
    prev         = first;
  }
  return dummy.next;
}

Time: O(n) | Space: O(1)


Q17 β€” Reverse Linked List II (Left to Right)

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Reverse a sublist between positions left and right.

Step 2 β€” Brute force (what NOT to do): Extract sublist, reverse, reattach.

Step 3 β€” Optimal insight: Walk to left-1, then insert-at-front for each step.

Step 4 β€” Algorithm:

1. Walk to node before position left

2. Insert each next node at the front of the reversed section

Step 5 β€” Edge cases: left = right (no-op), left = 1.

function reverseBetween(head, left, right) {
  const dummy = new ListNode(0, head);
  let prev = dummy;
  for (let i = 0; i < left - 1; i++) prev = prev.next;
  let curr = prev.next;
  for (let i = 0; i < right - left; i++) {
    const next  = curr.next;
    curr.next   = next.next;
    next.next   = prev.next;
    prev.next   = next;
  }
  return dummy.next;
}

Time: O(n) | Space: O(1)


Q18 β€” LRU Cache

🧠 How to Approach This

Step 1 β€” Recognize the pattern: O(1) get/put = Map + doubly linked list.

Step 2 β€” Brute force (what NOT to do): Array with access-time tracking β€” O(n) per op.

Step 3 β€” Optimal insight: Map for O(1) lookup, DLL for O(1) move-to-front and evict-from-tail.

Step 4 β€” Algorithm:

1. Map: key β†’ node

2. Get: move to front, return val

3. Put: update/insert at front; if over capacity remove tail

Step 5 β€” Edge cases: capacity = 1; updating existing key.

class LRUNode {
  constructor(key, val) { this.key = key; this.val = val; this.prev = this.next = null; }
}

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.map  = new Map();
    this.head = new LRUNode(0, 0); // sentinel
    this.tail = new LRUNode(0, 0); // sentinel
    this.head.next = this.tail;
    this.tail.prev = this.head;
  }
  get(key) {
    if (!this.map.has(key)) return -1;
    this._moveToFront(this.map.get(key));
    return this.map.get(key).val;
  }
  put(key, value) {
    if (this.map.has(key)) {
      this.map.get(key).val = value;
      this._moveToFront(this.map.get(key));
    } else {
      const node = new LRUNode(key, value);
      this.map.set(key, node);
      this._addToFront(node);
      if (this.map.size > this.capacity) {
        const removed = this._removeTail();
        this.map.delete(removed.key);
      }
    }
  }
  _addToFront(node) {
    node.next = this.head.next;
    node.prev = this.head;
    this.head.next.prev = node;
    this.head.next = node;
  }
  _removeNode(node) {
    node.prev.next = node.next;
    node.next.prev = node.prev;
  }
  _moveToFront(node) { this._removeNode(node); this._addToFront(node); }
  _removeTail() { const node = this.tail.prev; this._removeNode(node); return node; }
}

Time: O(1) get/put | Space: O(capacity)


Q19 β€” Reorder List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: 1β†’2β†’3β†’4β†’5 β†’ 1β†’5β†’2β†’4β†’3. Interleave first half with reversed second half.

Step 2 β€” Brute force (what NOT to do): Convert to array, reconstruct β€” O(n) space.

Step 3 β€” Optimal insight: (1) Find middle (2) Reverse second half (3) Merge alternating.

Step 4 β€” Algorithm:

1. Find middle (slow/fast, fast starts at head.next)

2. Reverse second half in-place

3. Merge: interleave nodes

Step 5 β€” Edge cases: Even vs odd length.

function reorderList(head) {
  if (!head || !head.next) return;
  let slow = head, fast = head;
  while (fast.next && fast.next.next) { slow = slow.next; fast = fast.next.next; }
  let prev = null, curr = slow.next; slow.next = null;
  while (curr) { const next = curr.next; curr.next = prev; prev = curr; curr = next; }
  let first = head, second = prev;
  while (second) {
    const tmp1 = first.next, tmp2 = second.next;
    first.next  = second;
    second.next = tmp1;
    first  = tmp1; second = tmp2;
  }
}

Time: O(n) | Space: O(1)


Q20 β€” Merge K Sorted Lists

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Divide and conquer β€” merge pairs repeatedly.

Step 2 β€” Brute force (what NOT to do): Collect all values, sort, rebuild β€” O(N log N), O(N) space.

Step 3 β€” Optimal insight: Pair up and merge β€” O(N log k), O(1) per merge.

Step 4 β€” Algorithm:

1. While lists.length > 1: pair up, merge each pair

2. Repeat until one list remains

Step 5 β€” Edge cases: Empty array, single list.

function mergeKLists(lists) {
  if (!lists.length) return null;
  while (lists.length > 1) {
    const merged = [];
    for (let i = 0; i < lists.length; i += 2) {
      merged.push(mergeSorted(lists[i], lists[i + 1] ?? null));
    }
    lists = merged;
  }
  return lists[0];
}

Time: O(N log k) | Space: O(1)


Q21 β€” Reverse Nodes in K-Group

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Reverse K nodes at a time; leave remainder if < K.

Step 2 β€” Brute force (what NOT to do): Collect to array, reverse groups, rebuild.

Step 3 β€” Optimal insight: Walk to kth node, reverse segment, repeat.

Step 4 β€” Algorithm:

1. Find kth node; if fewer than k β†’ stop

2. Reverse k nodes

3. Connect prev group to reversed head, advance

Step 5 β€” Edge cases: List not divisible by k.

function reverseKGroup(head, k) {
  const dummy = new ListNode(0, head);
  let groupPrev = dummy;
  while (true) {
    let kth = groupPrev;
    for (let i = 0; i < k; i++) {
      kth = kth.next;
      if (!kth) return dummy.next;
    }
    const groupNext = kth.next;
    let prev = groupNext, curr = groupPrev.next;
    while (curr !== groupNext) {
      const next = curr.next;
      curr.next  = prev;
      prev = curr; curr = next;
    }
    const tmp    = groupPrev.next;
    groupPrev.next = kth;
    groupPrev    = tmp;
  }
}

Time: O(n) | Space: O(1)


Q22 β€” Linked List Cycle II

Already covered in Q4 β€” Find Cycle Start.


Q23 β€” Maximum Twin Sum

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Find middle, reverse second half, compute twin sums.

Step 2 β€” Brute force (what NOT to do): Convert to array β€” O(n) space.

Step 3 β€” Optimal insight: Reverse second half in-place, walk both halves, track max.

Step 4 β€” Algorithm:

1. Find middle (slow/fast)

2. Reverse from middle onward

3. Walk both halves, compute and track max twin sum

Step 5 β€” Edge cases: Even length guaranteed by problem.

function pairSum(head) {
  let slow = head, fast = head;
  while (fast && fast.next) { slow = slow.next; fast = fast.next.next; }
  let prev = null, curr = slow;
  while (curr) { const next = curr.next; curr.next = prev; prev = curr; curr = next; }
  let max = 0, left = head, right = prev;
  while (right) {
    max   = Math.max(max, left.val + right.val);
    left  = left.next; right = right.next;
  }
  return max;
}

Time: O(n) | Space: O(1)


Q24 β€” Flatten Multilevel Doubly Linked List

🧠 How to Approach This

Step 1 β€” Recognize the pattern: When a child exists, splice child list inline.

Step 2 β€” Brute force (what NOT to do): Recursion with stack β€” harder to manage.

Step 3 β€” Optimal insight: Iterative: find child's tail, connect curr β†’ child β†’ curr.next.

Step 4 β€” Algorithm:

1. Walk curr; when child: find child's tail

2. Connect curr β†’ child, childTail β†’ curr.next; clear child pointer

Step 5 β€” Edge cases: Multiple nested levels; tail nodes.

function flatten(head) {
  let curr = head;
  while (curr) {
    if (curr.child) {
      let childTail = curr.child;
      while (childTail.next) childTail = childTail.next;
      const next        = curr.next;
      curr.next         = curr.child;
      curr.child.prev   = curr;
      childTail.next    = next;
      if (next) next.prev = childTail;
      curr.child = null;
    }
    curr = curr.next;
  }
  return head;
}

Time: O(n) | Space: O(1)


Q25 β€” Split Linked List in Parts

🧠 How to Approach This

Step 1 β€” Recognize the pattern: Distribute nodes into k parts, larger parts first.

Step 2 β€” Brute force (what NOT to do): Convert to array, split, rebuild.

Step 3 β€” Optimal insight: base = Math.floor(len/k), extra = len%k. First extra parts get base+1 nodes.

Step 4 β€” Algorithm:

1. Compute length

2. base = floor(len/k), extra = len%k

3. Walk list, cut at each part boundary

Step 5 β€” Edge cases: k > len (some nulls); k = 1.

function splitListToParts(head, k) {
  let len = 0, curr = head;
  while (curr) { len++; curr = curr.next; }
  const base = Math.floor(len / k), extra = len % k;
  const result = [];
  curr = head;
  for (let i = 0; i < k; i++) {
    result.push(curr);
    const size = base + (i < extra ? 1 : 0);
    for (let j = 0; j < size - 1 && curr; j++) curr = curr.next;
    if (curr) { const next = curr.next; curr.next = null; curr = next; }
  }
  return result;
}

Time: O(n + k) | Space: O(k)