π― 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 = previteratively.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
extraparts 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)