Linked List Interview Questions in Python: Reverse, Cycle Detection, Merge and More
Linked lists are small, which is why bugs hide in them: one pointer assigned in the wrong order and half the list is gone. Five problems cover most of what interviewers ask, and they reuse just three ideas: a dummy node, fast and slow pointers, and saving next before you overwrite it. Each problem below has tested Python code and a short note on why it works.
The node class
A singly linked list is a chain of nodes. Each node stores a value and a reference to the next node. The last node points to None.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
Unlike an array, you cannot jump to the middle in constant time. Reaching the k-th element takes k steps, so most problems are solved by walking the list once with one or more pointers.
Problem 1: reverse a linked list
Given the head of a list, reverse it and return the new head. The standard solution walks through the list and flips each pointer to face backwards.
def reverse_list(head):
prev = None
while head:
nxt = head.next # remember the rest of the list
head.next = prev # flip the pointer
prev = head # move prev forward
head = nxt # move head forward
return prev
Take the list 1, 2, 3. Start with prev = None. Step one makes node 1 point to None, step two makes node 2 point to node 1, and step three makes node 3 point to node 2. When head becomes None, prev is node 3, the new head. The key line is saving head.next before changing it, otherwise the rest of the list is lost. The time is O(n) and the space is O(1).
Problem 2: detect a cycle
Does the list loop back on itself? A set of visited nodes works but uses O(n) memory. Floyd’s tortoise and hare algorithm uses O(1) memory: move one pointer one step at a time and another two steps at a time. If there is a cycle, the fast pointer eventually catches up to the slow one inside the loop.
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
If there is no cycle, the fast pointer reaches the end and the loop stops. The condition fast and fast.next protects against errors on lists with an even or odd number of nodes. The time is O(n) and the space is O(1).
Problem 3: find the middle node
The same fast and slow idea finds the middle without counting the length first. When the fast pointer reaches the end, the slow pointer is at the middle.
def middle_node(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
For a list with an even number of nodes, this returns the second of the two middle nodes, which is what LeetCode expects. This helper is a building block for harder problems such as Palindrome Linked List and Reorder List.
Problem 4: merge two sorted lists
Combine two sorted lists into one sorted list. The trick is a dummy node: a placeholder at the start that removes special cases for the head of the result.
def merge_two_lists(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 whatever is left
return dummy.next
At each step we attach the smaller front node to the result and advance that list. When one list is empty, the remaining nodes of the other are already sorted, so we attach them in one move. The time is O(a + b), where a and b are the lengths, and the space is O(1) because we reuse the existing nodes.
Problem 5: remove the nth node from the end
You cannot count backwards in a singly linked list, so use two pointers with a gap. Move the fast pointer n steps ahead, then move both pointers together. When the fast pointer reaches the last node, the slow pointer sits just before the node to remove.
def remove_nth_from_end(head, n):
dummy = ListNode(0, head)
fast = slow = dummy
for _ in range(n):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
The dummy node makes removing the head work without a special case. This is a single pass, so the time is O(n) and the space is O(1).
Patterns to remember
- Dummy node: use it whenever the head of the result might change.
- Fast and slow pointers: middle, cycle detection and the nth node from the end.
- Save before you overwrite: store
node.nextbefore you change it. - Draw it: sketch three or four nodes and update arrows step by step.
Reversal traced one pointer at a time
Reversing a list is the problem where the order of assignments matters most, so it pays to see the state after every step. Here is the iterative version on 1 → 2 → 3, where prev starts as None and curr starts at the head.
| After | Reversed part | Still to process | prev |
curr |
|---|---|---|---|---|
| Start | empty | 1 → 2 → 3 | None | 1 |
| Step 1 | 1 | 2 → 3 | 1 | 2 |
| Step 2 | 2 → 1 | 3 | 2 | 3 |
| Step 3 | 3 → 2 → 1 | empty | 3 | None |
When the loop ends, curr has fallen off the list and prev is the new head. Inside each step, the order is always the same: save curr.next first, point curr backwards, then advance both pointers. Skip the save and the rest of the list is gone the moment you overwrite the link.
Five inputs to run before you say you are finished
- An empty list. Reversal should return
None, and cycle detection should return false without touchingfast.next. - A single node. Many loops that work for three nodes fail here.
- Two nodes. This is the smallest case where pointer order can go wrong.
- Removing the n-th node from the end when n equals the length. The head itself has to go, which is exactly what the dummy node is for.
- A list whose tail points back to the head. Cycle detection must still stop.
These problems also lead elsewhere. Merge k Sorted Lists is really a job for a heap, the fast and slow pointers are cousins of two pointers, and stack questions such as Next Greater Node lead to the monotonic stack.