← All Guides

Linked Lists

9 drills · 9 pattern exercises · 12 micro drills

Patterns Overview

Patterns used in this topic, with difficulty breakdown.

Pointer Walk 2 drills 2 Easy
prev/cur/nxt 1 drill 1 Easy
Slow/Fast 2 drills 1 Easy 1 Med
Slow/Fast + Reset 1 drill 1 Med
Dummy Head 1 drill 1 Easy
Hash Clone 1 drill 1 Med
Hash + DLL 1 drill 1 Med

Foundation Drill

9 Insert at position k Easy Pointer Walk

Add a new node at a specific position in a linked list.

Start here in Foundations →

Coding Drills

All 9 drills grouped by pattern. Each includes prompt, solution, and complexity.

Pointer Walk (2)

9 Insert at position k Easy

In plain English: Add a new node at a specific position in a linked list.

In a linked list, insertion is O(1) once you're at the right spot — the cost is in walking there.

Prompt

Insert value val at position k (0-indexed) in a linked list.

Solution

class Node:
    def __init__(self, data, next=None):
        self.data = data
        self.next = next

def insert_at(head, val, k):
    new = Node(val)
    if k == 0:
        new.next = head
        return new
    cur = head
    for _ in range(k - 1):
        cur = cur.next
    # splice new node in
    new.next = cur.next
    cur.next = new
    return head

# Test: 1->2->3, insert 9 at k=1 => 1->9->2->3
O(k) time · O(1) space
10 Delete node at position k Easy

In plain English: Remove the node at a specific position from a linked list.

Deletion is pointer surgery: make the previous node skip over the target by pointing to target.next.

Prompt

Delete the node at position k (0-indexed) in a linked list.

Solution

def delete_at(head, k):
    if k == 0:
        return head.next
    cur = head
    for _ in range(k - 1):
        cur = cur.next
    cur.next = cur.next.next  # skip over target node
    return head

# Test: 1->2->3->4, delete k=2 => 1->2->4
O(k) time · O(1) space

prev/cur/nxt (1)

11 Reverse a linked list Easy

In plain English: Make a linked list point backwards so the last node becomes the first.

Rewire each node to point backward by keeping three pointers — previous, current, and the saved next.

Prompt

Reverse a singly linked list in-place.

Solution

def reverse(head):
    prev, cur = None, head
    while cur:
        nxt = cur.next  # save next before rewiring
        cur.next = prev
        prev = cur
        cur = nxt
    return prev

# Test: 1->2->3->4 => 4->3->2->1
O(n) time · O(1) space

Slow/Fast (2)

12 Detect cycle (Floyd's) Medium

In plain English: Figure out if a linked list loops back on itself instead of ending at null.

If there's a cycle, a fast pointer (2x speed) will eventually lap the slow pointer — like two runners on a circular track.

Prompt

Detect if a linked list has a cycle using O(1) space.

Solution

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        # fast laps slow if cycle exists
        if slow == fast:
            return True
    return False
O(n) time · O(1) space
14 Find middle node Easy

In plain English: Find the middle element of a linked list in one pass without knowing its length.

When the fast pointer reaches the end at 2x speed, the slow pointer is exactly at the midpoint — one pass, no length needed.

Prompt

Find the middle node of a linked list.

Solution

def find_middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    # fast at end = slow at middle
    return slow

# Test: 1->2->3->4->5 => node(3)
O(n) time · O(1) space

Slow/Fast + Reset (1)

13 Find cycle start Medium

In plain English: Find exactly which node a linked list starts looping at.

After the pointers meet inside the cycle, the distance from head to cycle start equals the distance from the meeting point to cycle start — a property of Floyd's algorithm.

Prompt

Find the node where the cycle begins in a linked list.

Solution

def cycle_start(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            slow = head
            # equal distance to cycle start
            while slow != fast:
                slow = slow.next
                fast = fast.next
            return slow
    return None
O(n) time · O(1) space

Dummy Head (1)

15 Merge two sorted lists Easy

In plain English: Combine two sorted linked lists into one sorted linked list.

A dummy head node eliminates edge cases for the first insertion — you just return dummy.next at the end.

Prompt

Merge two sorted linked lists into one sorted list.

Solution

def merge_sorted(a, b):
    dummy = tail = Node(0)  # dummy avoids empty-list edge case
    while a and b:
        if a.data <= b.data:
            tail.next = a; a = a.next
        else:
            tail.next = b; b = b.next
        tail = tail.next
    tail.next = a or b
    return dummy.next

# Test: 1->3->5 + 2->4->6 => 1->2->3->4->5->6
O(n+m) time · O(1) space

Hash Clone (1)

137 Copy List with Random Pointer Medium

In plain English: Clone a linked list where each node also has a random pointer to any other node in the list.

First pass: create a clone of each node and store the mapping old->new in a hash map. Second pass: wire up next and random pointers using the map. The map is the key to handling random pointers.

Prompt

A linked list has nodes with a next pointer and a random pointer (which can point to any node or null). Return a deep copy of the list.

Solution

class Node:
    def __init__(self, val=0, next=None, random=None):
        self.val = val
        self.next = next
        self.random = random

def copy_random_list(head):
    if not head:
        return None
    old_to_new = {}
    # Pass 1: create cloned nodes
    cur = head
    while cur:
        old_to_new[cur] = Node(cur.val)
        cur = cur.next
    # Pass 2: set next and random pointers
    cur = head
    while cur:
        clone = old_to_new[cur]
        clone.next = old_to_new.get(cur.next)
        clone.random = old_to_new.get(cur.random)
        cur = cur.next
    return old_to_new[head]

# Test: list 7->13->11, 13.random=7, 11.random=13
# deep copy preserves structure with new node objects
O(n) time · O(n) space

Hash + DLL (1)

138 LRU Cache Medium

In plain English: Build a cache with a fixed capacity. When full, evict the least recently used item. Both get and put should be O(1).

Combine a hash map (O(1) lookup) with a doubly linked list (O(1) removal and insertion). The list maintains usage order — most recent at head, least recent at tail. On access, move to head; on eviction, remove from tail.

Prompt

Design a data structure that follows the Least Recently Used (LRU) cache eviction policy. Implement get(key) and put(key, value) in O(1) time.

Solution

class DLLNode:
    def __init__(self, key=0, val=0):
        self.key = key
        self.val = val
        self.prev = None
        self.next = None

class LRUCache:
    def __init__(self, capacity):
        self.cap = capacity
        self.cache = {}
        self.head = DLLNode()  # dummy head
        self.tail = DLLNode()  # dummy tail
        self.head.next = self.tail
        self.tail.prev = self.head

    def _remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def _add_front(self, node):
        node.next = self.head.next
        node.prev = self.head
        self.head.next.prev = node
        self.head.next = node

    def get(self, key):
        if key not in self.cache:
            return -1
        node = self.cache[key]
        self._remove(node)
        self._add_front(node)  # mark as recently used
        return node.val

    def put(self, key, value):
        if key in self.cache:
            self._remove(self.cache[key])
        node = DLLNode(key, value)
        self._add_front(node)
        self.cache[key] = node
        if len(self.cache) > self.cap:
            lru = self.tail.prev
            self._remove(lru)
            del self.cache[lru.key]

# Test: c = LRUCache(2)
# c.put(1,1); c.put(2,2); c.get(1) == 1
# c.put(3,3); c.get(2) == -1 (evicted)
O(1) per operation · O(capacity) space

Pattern Recognition

9 exercises: spot the signals, pick the method, avoid the trap.

9 Insert at position k Easy

Insert value val at position k (0-indexed) in a linked list.

Signals

Pointer Walk

Walk to node k-1, set new_node.next = node.next, node.next = new_node. O(k) time.

Trap

Don't forget the edge case: inserting at position 0 (head) needs special handling or a dummy node.

10 Delete node at position k Easy

Delete the node at position k (0-indexed) in a linked list.

Signals

Pointer Walk

Walk to node k-1, set prev.next = prev.next.next. O(k) time.

Trap

Forgetting to handle deletion of the head node (k=0) or going past the end.

11 Reverse a linked list Easy

Reverse a singly linked list in-place.

Signals

Two Pointers

prev/cur/next pattern: save next, point cur back to prev, advance both. O(n) time, O(1) space.

DFS / Recursion

Recursive reversal: reverse the rest, then make cur.next.next = cur. Elegant but O(n) stack space.

Trap

Don't lose the reference to next_node before rewiring — save it first, then modify cur.next.

12 Detect cycle (Floyd's) Medium

Detect if a linked list has a cycle using O(1) space.

Signals

Two Pointers

Floyd's: slow moves 1 step, fast moves 2. If cycle exists, they meet. O(n) time, O(1) space.

Hash Map

Store visited nodes in a set — O(n) time but O(n) space. Violates the O(1) space constraint.

Trap

The O(1) space constraint is the key signal. Without it, hash set is fine.

13 Find cycle start Medium

Find the node where the cycle begins in a linked list.

Signals

Two Pointers

After detection: reset slow to head, advance both by 1. They meet at cycle start. O(n).

Hash Map

Store each visited node; first repeat is cycle start. O(n) space.

Trap

This is a two-phase algorithm: detect first, then find start. Don't try to do both in one pass.

14 Find middle node Easy

Find the middle node of a linked list.

Signals

Two Pointers

Slow (1 step) and fast (2 steps). When fast hits end, slow is at the middle. One pass, O(1) space.

Trap

Counting length then iterating to n/2 works but requires two passes. Slow/fast does it in one.

15 Merge two sorted lists Easy

Merge two sorted linked lists into one sorted list.

Signals

Two Pointers

Compare heads of both lists, append the smaller to result. Dummy head simplifies the code. O(n+m).

DFS / Recursion

Recursive merge: pick smaller head, recurse on remaining. Clean code but O(n+m) stack space.

Trap

Don't forget to append the remaining list when one is exhausted.

137 Copy List with Random Pointer Medium

Deep copy a linked list where each node has a next pointer and a random pointer to any node.

Signals

Hash Map

First pass: map each original → clone. Second pass: set next and random using the map. O(n).

Pointer Walk

Interleave clones: A→A'→B→B'→... Set random pointers, then separate. O(1) space.

Trap

Don't forget to handle None random pointers. The hash map approach is cleaner than interleaving.

138 LRU Cache Medium

Design a data structure for Least Recently Used cache with O(1) get and put.

Signals

Hash Map

OrderedDict or HashMap + doubly linked list. Get moves to end. Put evicts front if over capacity. O(1).

Trap

A regular dict doesn't track access order. You need either OrderedDict or a manual DLL + HashMap.

Related Micro Drills

12 quick recall drills to reinforce this topic.

23 Insert at head Pointer Manipulation 10s

Head insertion is O(1) and builds lists in reverse. Core for reverse-linked-list.

new = Node(val)
new.next = head
head = new
20 Traverse to end Pointer Manipulation 10s

Linear traversal is how you access linked list elements. Basis for search and length calculation.

while cur: cur = cur.next
22 Append to tail Pointer Manipulation 10s

Tail insertion requires traversal to end. Know this for list construction problems.

while cur.next:
    cur = cur.next
cur.next = Node(val)
24 Delete node by value Pointer Manipulation 15s

Dummy-head deletion avoids special-casing the head node. Essential linked list technique.

dummy = Node(0)
dummy.next = head
prev, cur = dummy, head
while cur:
    if cur.val == target:
        prev.next = cur.next
        break
    prev, cur = cur, cur.next
head = dummy.next
137 Clone linked list with random Hash Strategies 15s

Two-pass hash map maps old nodes to new clones. Second pass wires next/random.

d = {None: None}
n = head
while n:
    d[n] = Node(n.val)
    n = n.next
n = head
while n:
    d[n].next = d[n.next]
    d[n].random = d[n.random]
    n = n.next
218 Flatten BT to linked list Tree Traversal 15s

Morris-like: for each node with left child, find its inorder predecessor, link right subtree there, move left to right.

def flatten(root):
    cur = root
    while cur:
        if cur.left:
            # find rightmost of left subtree
            prev = cur.left
            while prev.right:
                prev = prev.right
            # rewire
            prev.right = cur.right
            cur.right = cur.left
            cur.left = None
        cur = cur.right
271 Floyd's cycle detection (find start) Pointer Manipulation 15s

Phase 1: detect cycle with slow/fast. Phase 2: reset slow to head, advance both by 1 — they meet at cycle start.

slow = fast = head
while fast and 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  # cycle start
25 Find middle (slow/fast) Pointer Manipulation 10s

Fast/slow pointer finds the middle in one pass. Core technique for merge sort on lists.

slow, fast = head, head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
# slow is at the middle
21 Find length Pointer Manipulation 10s

Length calculation requires full traversal. Used as a helper in remove-nth-from-end.

length = 0
while cur:
    length += 1
    cur = cur.next
76 Merge two sorted lists Divide & Conquer 15s

Merging sorted lists is the core of merge sort and merge-k-lists.

i = j = 0
merged = []
while i < len(a) and j < len(b):
    if a[i] <= b[j]:
        merged.append(a[i]); i += 1
    else:
        merged.append(b[j]); j += 1
merged.extend(a[i:])
merged.extend(b[j:])
19 Create a node Pointer Manipulation 5s

The node class is the foundation for all linked list operations. Know the constructor cold.

class Node:
    def __init__(self, val, nxt=None):
        self.val = val
        self.next = nxt
138 LRU cache get/put skeleton Hash Strategies 15s

OrderedDict gives O(1) get/put with LRU eviction. Classic system design building block.

from collections import OrderedDict
class LRU:
    def __init__(self, cap):
        self.cap = cap
        self.d = OrderedDict()
    def get(self, k):
        if k not in self.d: return -1
        self.d.move_to_end(k)
        return self.d[k]
    def put(self, k, v):
        self.d[k] = v
        self.d.move_to_end(k)
        if len(self.d) > self.cap:
            self.d.popitem(last=False)