Linked List Interview Pointer Bugs and How to Avoid Them

Linked List Interview Pointer Bugs and How to Avoid Them

Linked list problems look easy on paper: a handful of nodes and a few pointers. In a live interview they cause more silent failures than almost any other topic. You lose the rest of the list, dereference None, remove the wrong node, or loop forever. Each bug takes one line to introduce and five minutes to find.

This guide covers the most common linked list interview pointer bugs, the correct pattern for each, and a dry-run habit that catches them before your interviewer does. The examples are in Python, but the same logic applies in Java, C++, JavaScript, or Go.

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

Bug 1: losing the rest of the list

The classic reversal mistake is overwriting curr.next before saving it:

# Buggy
while curr:
    curr.next = prev      # the rest of the list is now unreachable
    prev = curr
    curr = curr.next      # now the old prev, not the old next

Save first, then rewire:

def reverse(head):
    prev, curr = None, head
    while curr:
        nxt = curr.next   # 1. save
        curr.next = prev  # 2. rewire
        prev = curr       # 3. advance prev
        curr = nxt        # 4. advance curr
    return prev

Say the four steps out loud as you write them: "save, rewire, advance, advance." It's hard to get the order wrong while you're narrating it.

Bug 2: tuple assignment in the wrong order

Python's multiple assignment looks safe, but the targets are assigned left to right:

# Correct: the right side is evaluated first, then curr.next is set while curr is still the old node
curr.next, prev, curr = prev, curr, curr.next

# Buggy: curr is reassigned before curr.next, so the wrong node's pointer is changed
prev, curr, curr.next = curr, curr.next, prev

In the buggy version, curr.next = prev runs on the new curr. It rewires the wrong node, and a step later it tries to set .next on None and crashes. If you're nervous, use the explicit four-line version. Clarity beats cleverness in an interview.

Bug 3: special-casing the head instead of using a dummy

Deleting or inserting at the head needs a special case unless you add a dummy (sentinel) node:

def remove_value(head, target):
    dummy = ListNode(0, head)
    prev = dummy
    while prev.next:
        if prev.next.val == target:
            prev.next = prev.next.next   # don't advance; check the new next
        else:
            prev = prev.next
    return dummy.next

Two pointer bugs are hidden here:

Say: "I'll use a dummy node so removing the head isn't a special case."

Bug 4: fast/slow null checks

Finding the middle with two pointers:

def middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow   # second middle for even length

Common mistakes:

If you need the first middle (for example, to split the list in half), start fast at head.next:

slow, fast = head, head.next
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
# slow is the end of the first half (assuming head is not None)

Bug 5: off-by-one in "remove nth from end"

Move fast n + 1 steps ahead of a dummy, so that slow stops before the node to remove:

def remove_nth_from_end(head, n):
    dummy = ListNode(0, head)
    fast = slow = dummy
    for _ in range(n + 1):
        fast = fast.next
    while fast:
        fast = fast.next
        slow = slow.next
    slow.next = slow.next.next
    return dummy.next

This assumes 1 ≤ n ≤ length. Say so, or ask. Starting from head instead of dummy, or moving n steps instead of n + 1, removes the wrong node or crashes when n equals the length.

Bug 6: cycle detection that crashes or never ends

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            return True
    return False

Mistakes to avoid:

Bug 7: merging without a tail pointer

def merge(a, b):
    dummy = tail = ListNode()
    while a and b:
        if a.val <= b.val:
            tail.next, a = a, a.next
        else:
            tail.next, b = b, b.next
        tail = tail.next
    tail.next = a or b    # attach the leftover list
    return dummy.next

The usual bugs are forgetting tail = tail.next (everything overwrites one link) and forgetting to attach the leftover nodes. Using <= keeps the merge stable.

Bug 8: accidental cycles after rewiring

When you split, reorder, or partition a list, the last node of a new list may still point into the old one:

# After building a "less than x" list and a "greater or equal" list:
greater_tail.next = None        # cut the old link
less_tail.next = greater_dummy.next

Forgetting the None creates a cycle, and the interviewer's test loops forever.

The dry-run habit that catches all of these

Before you say "done," trace a tiny list by drawing it:

dummy -> 1 -> 2 -> 3 -> None
         ^prev ^curr

Check three inputs:

  1. Empty list (head is None)
  2. Single node
  3. Two or three nodes, including the case where the head itself changes

At each step, say which pointer moves and where it points. Most pointer bugs show up within the first two iterations.

Talk tracks

"I'll save curr.next before rewiring so I don't lose the rest of the list."

"A dummy node means the head isn't a special case. I'll return dummy.next."

"The loop condition checks fast before fast.next to avoid dereferencing None."

Soft practice note

Pointer bugs are a pressure problem more than a knowledge problem. Most people can reverse a list calmly, and fewer can do it with someone watching and a clock running. If you want timed, out-loud practice sessions to make these patterns automatic, Luna Interview is built for calm, structured interview rehearsal and prep before the real round.

Pre-interview checklist

Linked lists reward slow, deliberate pointer moves. Narrate each one, and the bugs mostly go away.