← Back to Course

Top Interview Questions for Linked Lists

Top 5 Linked List Variants & Patterns

Top 5 Linked List Variants & Patterns

Frequently Asked in LeetCode Interviews

Introduction

Linked lists are a common topic in coding interviews. Below are five highly frequent linked list problem patterns from LeetCode, each representing a twist on basic list operations. We outline the variant name, the modified pointer technique (how it differs from basic traversal), and an example problem with a step-by-step solution.

Linked List Variants

1. In-Place Reversal of a Linked List

Modified Template: Instead of a standard forward traversal, this pattern reverses the direction of next pointers as it iterates. It uses three pointers: prev, curr, and next. Initially prev = null and curr = head. At each step, store curr.next in next, then redirect curr.next to prev (reversing the link), and move prev and curr one step forward. This in-place pointer flip continues until all nodes point backwards (medium.com). The new head will be the original tail (prev at loop end).

In-place reversal of a singly linked list. Initially, prev = ∅ and curr = 1. The pointer from 1 is flipped to point to null (new tail). Subsequently, each node’s next is reversed in turn (2’s next points to 1, 3’s next points to 2, etc.), resulting in a fully reversed list.

Example Problem: LeetCode 206 – Reverse Linked List – Given the head of a singly linked list, reverse the list and return the new head.

Walkthrough: Consider the list 1 → 2 → 3 → ∅. We’ll reverse it:
  1. Initialization: prev = ∅, curr = 1. (List: 1 → 2 → 3 → ∅)
  2. Iteration 1: Save next = curr.next (node 2). Set curr.next = prev (1’s next becomes ∅). Move prev to curr (prev = node 1) and curr to next (curr = node 2). (List now looks like 1 ← ∅     2 → 3 → ∅; prev at 1, curr at 2)
  3. Iteration 2: Save next = 3. Set curr.next = prev (2’s next -> 1). Move prev = 2, curr = 3. (List: 1 ← 2     3 → ∅; prev at 2, curr at 3)
  4. Iteration 3: Save next = ∅. Set curr.next = prev (3’s next -> 2). Move prev = 3, curr = ∅. (List: 1 ← 2 ← 3; prev at 3, curr is null, loop ends)
  5. Result: prev points to new head (node 3). The reversed list is 3 → 2 → 1 → ∅. Every link was reversed in-place.
// Java Solution Code
class ListNode {
    int val;
    ListNode next;
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode nextTemp = curr.next; // Store next node
        curr.next = prev;           // Reverse current node's pointer
        prev = curr;                // Move prev one step forward
        curr = nextTemp;            // Move curr one step forward
    }
    return prev; // prev will be the new head
}

💡 Variants: Reversing sub-parts of a list follows a similar template with pointer adjustments. For example, Reverse Nodes in k-Group reverses every k nodes by repeatedly applying this technique on sublists. It requires tracking the nodes before and after each segment to reconnect the reversed parts.

2. Fast & Slow Pointers (Tortoise & Hare Technique)

Modified Template: This pattern introduces two pointers moving at different speeds through the list. A slow pointer moves one node at a time, and a fast pointer moves two nodes at a time (blog.algomaster.io). By advancing differently, we can detect certain properties:

  • In a cyclic list, the fast pointer will eventually lap the slow pointer and meet it if a cycle exists (blog.algomaster.io).
  • In a non-cyclic list, the fast pointer reaches the end (null) while slow is at the middle node.

This technique thus helps in cycle detection, finding the middle of a list, checking palindromes, etc., all in one pass.

Fast & slow pointer illustration: the fast pointer (blue) moves two steps at a time, while the slow pointer (green) moves one step. In a cyclic linked list, the fast pointer will eventually catch up to the slow pointer, indicating a cycle. (blog.algomaster.io)

Example Problem: LeetCode 141 – Linked List Cycle – Determine if a linked list contains a cycle (a node that links back to a previous node). Use O(1) extra space.

Walkthrough: Suppose we have a list 1 → 2 → 3 → 4 → 5 → … that eventually cycles back to node 3. We use slow and fast:
  1. Start: slow = 1, fast = 1.
  2. Step 1: Move slow to 2, fast to 3 (fast jumps two nodes) (blog.algomaster.io). (slow=2, fast=3)
  3. Step 2: Move slow to 3, fast to 5 (fast goes from 3 -> 4 -> 5). (slow=3, fast=5)
  4. Step 3: Move slow to 4, fast next would go from 5 -> (back to) 3 -> 4, so fast becomes 4. Now slow == fast at node 4, meaning the fast pointer caught up to slow within the list – a cycle is confirmed.
  5. If the list had no cycle, fast would hit null eventually. Here it didn’t, and instead met slow, indicating a loop.
// Java Solution Code
public boolean hasCycle(ListNode head) {
    if (head == null) return false;
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true; // Pointers meet, cycle exists
    }
    return false; // Fast pointer reached end, no cycle
}

💡 Variants: The fast/slow pattern also finds the middle node of a list by stopping when fast hits the end (medium.com). It is used in palindrome checks (find mid, reverse second half, compare) and finding the cycle start (Floyd’s cycle algorithm extension). These use the same two-pointer concept with slight modifications in post-processing.

3. Two-Pointer Offset (Nth-from-End Technique)

Modified Template: This uses two pointers starting together but then offsetting one by n steps. By the time the leading pointer hits the end, the trailing pointer will be at the (n+1)th last node. This pattern is commonly used to remove or find the k-th node from the end in one pass (medium.com). A dummy head is often used to simplify edge cases where the head might be removed.

Procedure: Initialize two pointers fast and slow at the head. Advance fast by n nodes. Then move both fast and slow one step at a time until fast reaches the end (null). At that point, slow is right before the target node. Adjust pointers to remove or return the target node as needed (medium.com).

Example Problem: LeetCode 19 – Remove Nth Node From End of List – Remove the n-th node from the end of a linked list and return the head of the modified list.

Walkthrough: Consider list A → B → C → D → E and n = 2 (remove 2nd from end, which is D):
  1. Initial: Use a dummy node pointing to A. slow = dummy, fast = dummy. (List: dummy → A → B → C → D → E)
  2. Offset fast by n+1 steps: Move fast 3 steps ahead (for n=2). fast points to C. (slow at dummy, fast at C). (medium.com)
  3. Move in tandem until fast reaches the end (fast.next == null for fast to be at last node, or fast == null if fast goes beyond last node):
    • Move: slow to A, fast to D.
    • Move: slow to B, fast to E.
    • fast is now at E (last node). Loop condition (while (fast != null) in typical code) will run once more.
    • Move: slow to C, fast to ∅. Loop terminates.
  4. Now slow is at C, which is the node *before* the target node D.
  5. Removal: Skip the target node: set slow.next = slow.next.next (point C’s next to E, dropping D). The list becomes dummy → A → B → C → E.
  6. Return dummy.next (which is A).
// Java Solution Code
public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode slow = dummy;
    ListNode fast = dummy;

    // Move fast n+1 steps ahead
    for (int i = 0; i <= n; i++) {
        if (fast == null) { // Handles cases where n is too large or list is short
             return head; // Or throw error, depending on problem spec for invalid n
        }
        fast = fast.next;
    }

    // Move both pointers until fast reaches the end of the list
    while (fast != null) {
        slow = slow.next;
        fast = fast.next;
    }

    // Now slow is the node just before the Nth node from the end
    if (slow != null && slow.next != null) {
        slow.next = slow.next.next; // Skip the Nth node
    }
    
    return dummy.next;
}

💡 Variants: The two-pointer offset pattern also helps find the k-th node from the end (without removal). A related trick is finding the intersection of two linked lists: by advancing two pointers and then resetting one to the other list’s head, they align after the length difference and meet at the intersection node (medium.com). Both techniques rely on offsetting pointer positions to sync up at a target.

4. Dummy Head & Sorted Merge Pattern

Modified Template: Many list problems are easier if you use a dummy head (sentinel) to handle edge cases uniformly. In merging or constructing new lists, the dummy’s next will eventually be the real head of the result. Maintain a tail pointer starting at the dummy. Iterate through input lists, and at each step, attach the next node to tail.next and advance tail. This avoids having to treat the first node as a special case since the dummy precedes the result list (medium.com).

Example Problem: LeetCode 21 – Merge Two Sorted Lists – Merge two sorted linked lists into one sorted list.

Walkthrough: Suppose List1 is 1 → 3 → 5 and List2 is 2 → 4 → 6. We use a dummy node (with value say 0) and let tail = dummy:
  1. Start: tail at dummy (∅ → dummy). List1 at 1, List2 at 2.
  2. Compare List1 and List2 heads (1 vs 2). Attach smaller (1) to tail.next. Now result list is dummy → 1. Advance List1 pointer to 3. Move tail to the newly added node (tail = node 1).
  3. Compare List1 (3) vs List2 (2). Attach smaller (2) to tail.next. Result becomes dummy → 1 → 2. Advance List2 to 4, move tail to node 2.
  4. Compare 3 vs 4. Attach 3 → result dummy → 1 → 2 → 3. Advance List1 to 5, tail to 3.
  5. Compare 5 vs 4. Attach 4 → result ... → 3 → 4. Advance List2 to 6, tail to 4.
  6. Compare 5 vs 6. Attach 5 → result ... → 4 → 5. Advance List1 to ∅ (end), tail to 5.
  7. One list is exhausted (List1). Append remaining List2 (node 6) to result. Result becomes dummy → 1 → 2 → 3 → 4 → 5 → 6.
  8. Return dummy.next (head of merged list, node 1). The merged list (excluding dummy) is 1 → 2 → 3 → 4 → 5 → 6.
// Java Solution Code
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0);
    ListNode tail = dummy;

    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            tail.next = l1;
            l1 = l1.next;
        } else {
            tail.next = l2;
            l2 = l2.next;
        }
        tail = tail.next;
    }

    // Attach whichever list remains
    if (l1 != null) {
        tail.next = l1;
    } else {
        tail.next = l2;
    }

    return dummy.next;
}

💡 Variants: Using a dummy head simplifies list insertion and combination problems. It appears in adding numbers represented by lists (to build the sum list) (medium.com, medium.com), in partitioning a list (building two sublists then joining), and in many merge-related tasks. The dummy is a convenient placeholder to avoid handling empty-result or head-update cases separately.

5. Cloning a List with Random/Arbitrary Pointers

Modified Template: This covers linked lists that have extra pointers besides the normal next (e.g. a random pointer that can point to any node or null). The challenge is to copy the complex pointers structure. A typical solution uses multiple passes with pointer manipulation:

  1. Interweave clone nodes: For each original node, create a new node and insert it right after the original node in the list (algo.monster). This weaves the original and cloned nodes in one list.
  2. Set random pointers: Traverse the interwoven list and for each original node, set its clone’s random pointer. If original’s random points to node X, then clone’s random should point to X’s clone (which is X.next in the interwoven list) (algo.monster).
  3. Detach the clone list: Restore the original list and extract the cloned nodes by reconnecting clone nodes together in their own list (algo.monster).

Example Problem: LeetCode 138 – Copy List with Random Pointer – Each list node has an additional random pointer to any node (or null). Return a deep copy of the list.

Walkthrough: Consider a list: 1 → 2 → 3 → ∅, where random pointers are: 1.rand → 2, 2.rand → ∅, 3.rand → 1. We’ll create a cloned list step by step:
  1. Interleave Clones: Clone each node and insert right after it:
    • Clone of 1 (call it 1′) inserted after 1.
    • Clone of 2 (2′) inserted after 2.
    • Clone of 3 (3′) inserted after 3.
    The list now looks like: 1 → 1′ → 2 → 2′ → 3 → 3′ → ∅. (Original and clone nodes alternate.) (algo.monster)
  2. Set Random Pointers: Iterate again:
    • For original 1, which had random → 2, set 1′.random → 2′ (the clone of 2) (algo.monster).
    • Original 2’s random is null, so 2′.random = null.
    • Original 3’s random → 1, so set 3′.random → 1′.
    Now all clone nodes have correct random pointers.
  3. Detach Lists: Split the interwoven list into two:
    • Restore original list: 1 → 2 → 3 → ∅ (by skipping out the clones).
    • Extract cloned list: 1′ → 2′ → 3′ → ∅ (with clone pointers all set) (algo.monster).
    The cloned list is a deep copy of the original, with identical pointer structure.
# Python Solution Code
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return None

        # 1. Interleave cloned nodes with original nodes
        curr = head
        while curr:
            newNode = Node(curr.val)
            newNode.next = curr.next
            curr.next = newNode
            curr = newNode.next
        
        # 2. Set random pointers of clones
        curr = head
        while curr:
            if curr.random:
                curr.next.random = curr.random.next # curr.random.next is the clone of curr.random
            curr = curr.next.next # Move to the next original node
            
        # 3. Detach the clone list from the original list
        curr = head
        clone_head = head.next
        while curr:
            clone_node = curr.next
            curr.next = clone_node.next # Restore original list's next pointer
            
            if clone_node.next:
                clone_node.next = clone_node.next.next # Set clone list's next pointer
            
            curr = curr.next # Move to the next original node
            
        return clone_head

💡 Variants: Flattening a multilevel linked list (where each node may have a child list) is another pointer-intensive problem. It can be solved by traversing depth-first and splicing child lists into the main list (medium.com, medium.com). Design problems like LRU Cache use a doubly-linked list with extra pointers for quick eviction. Whenever nodes have additional pointers or multi-level structure, the solution often involves carefully updating those pointers in-place (sometimes using a stack or dummy nodes) to achieve the desired list transformation.

Sources: Key patterns and examples are drawn from popular LeetCode interview questions (linkedin.com, linkedin.com) and canonical solutions (medium.com, medium.com, medium.com, medium.com). Techniques like fast/slow pointers and in-place reversal are well-documented for cycle detection (blog.algomaster.io) and list manipulation (blog.algomaster.io). The random-pointer cloning strategy is described in-depth in problem explanations (algo.monster, algo.monster). These patterns cover the most frequent and insightful Linked List variations seen in coding interviews.

Interactive Linked List Animations

Prev: null, Curr: N/A, NextTemp: N/A

Enter list values (e.g. 1,2,3).

© Linked List Variants Explained. Content adapted for educational purposes.

We use cookies to improve your experience. By clicking “Accept” you consent to the use of cookies. Read our Privacy Policy.