Coding Interview Patterns Module 10 – Linked list patterns
Fast and slow pointers: middle and cycles
Find the middle of a linked list in one pass, detect a loop and the node where it starts, and find a repeated number in a read-only array.
What you will learn
- Find the middle node of a list in one pass, and choose the first or the second middle on purpose
- Detect a loop and the node where it starts with Floyd's method, in O(n) time and O(1) memory
- Prove why the second walk of Floyd's method stops exactly at the loop's first node
- Apply cycle detection to an array read as a chain, to find a repeated number without changing the array
- Compare Floyd's and Brent's methods by the number of steps each one needs
Before you start
What you’ll learn · Before you start
What you will learn
- Find the middle node of a list in one pass, and choose the first or the second middle on purpose
- Detect a loop and the node where it starts with Floyd's method, in O(n) time and O(1) memory
- Prove why the second walk of Floyd's method stops exactly at the loop's first node
- Apply cycle detection to an array read as a chain, to find a repeated number without changing the array
- Compare Floyd's and Brent's methods by the number of steps each one needs
Before you start
On this page
Fast and slow pointers are two references that walk the same chain at different speeds: the slow one moves one node a step, the fast one two. If the chain ends, the fast pointer gets there when the slow one is in the middle. If the chain loops, the fast pointer gains one node a step and catches the slow one inside the loop, and a second walk from the start stops where the loop begins: Floyd’s cycle method, with O(n) time and O(1) memory. The chain need not be a linked list. A list whose values are positions in that list is one too, which is how the method finds a repeated number among n + 1 values from 1 to n without changing them, the problem known as find the duplicate number.
Two problems, one chain
Problem A: a redirect loop. A link shortener resolves each short link through a chain of redirects: every node
holds an address and the next node, or None at the final address. A misconfigured chain loops back on itself. Given
the first node, return None if the chain ends, or the first node of its loop, with O(1) extra memory and without
changing any node. A chain has up to 10⁶ nodes. So a → b → c → d → e → c gives c, and a → b → c gives None.
Problem B: a ticket printed twice. A machine printed n + 1 tickets, each numbered from 1 to n, so some number was
printed more than once, and exactly one number was. Return it with O(1) extra memory, without changing the list;
1 ≤ n ≤ 10⁵. So [4, 2, 5, 1, 3, 5] gives 5, [3, 3, 3, 3] gives 3, and [2, 6, 4, 1, 3, 6, 5] gives 6.
The brute force, and why it fails at these sizes
Without memory for visited nodes, the direct method for A walks the chain and, at the k-th node, walks k nodes from the start again to see whether it came before: about n²/2 steps, 5 × 10¹¹ for a million nodes. For B, count each number from 1 to n: about 10¹⁰ comparisons at the limit. A set of visited items, or a sorted copy, is fast but needs O(n) memory, and sorting the list itself changes it.
brute/brute_force.py
"""Brute force for both problems, with the work counted.
A: the first node of a redirect loop, using O(1) memory: walk the chain, and at each step walk again from the start
to check whether the node just reached was seen before. B: the repeated ticket, by counting every possible number.
"""
from chains import chain
def loop_start_brute(first):
"""Return (the first node seen twice, or None, steps). Step k walks k nodes from the start again."""
steps, node, k = 0, first, 0
while node is not None:
probe = first
for _ in range(k): # is node among the k nodes before it?
steps += 1
if probe is node:
return node, steps
probe = probe.next
node, k = node.next, k + 1
return None, steps
def repeated_ticket_brute(tickets):
"""Return (the number that appears twice or more, comparisons), counting each number from 1 to n."""
comparisons = 0
for v in range(1, len(tickets)):
count = 0
for t in tickets:
comparisons += 1
count += t == v
if count > 1:
return v, comparisons
return None, comparisons
start, steps = loop_start_brute(chain("abcde", loop_to=2))
print(f"A: a -> b -> c -> d -> e -> back to c: the loop starts at {start.address} ({steps} steps)")
start, steps = loop_start_brute(chain("abc"))
print(f"A: a -> b -> c with no loop: {start} ({steps} steps)")
for n in (500, 1_000): # a tail of n/2 nodes, then a loop of n/2 nodes
kept = chain(range(n), loop_to=n // 2)
_, steps = loop_start_brute(kept)
print(f"A: {n:>5,} nodes: {steps:>9,} steps")
for tickets in ([4, 2, 5, 1, 3, 5], [3, 3, 3, 3], [2, 6, 4, 1, 3, 6, 5]):
print(f"B: {tickets} -> {repeated_ticket_brute(tickets)[0]}")
for n in (1_000, 2_000): # the repeated number is n, the last one the brute force counts
_, comparisons = repeated_ticket_brute(list(range(1, n + 1)) + [n])
print(f"B: n = {n:>5,}: {comparisons:>9,} comparisons")
n = 100_000
print(f"B: n = {n:,}: about {n * (n + 1):,} comparisons, {n * (n + 1) / 1e7 / 60:.0f} minutes at 10^7 a second") Output
A: a -> b -> c -> d -> e -> back to c: the loop starts at c (13 steps) A: a -> b -> c with no loop: None (3 steps) A: 500 nodes: 125,001 steps A: 1,000 nodes: 500,001 steps B: [4, 2, 5, 1, 3, 5] -> 5 B: [3, 3, 3, 3] -> 3 B: [2, 6, 4, 1, 3, 6, 5] -> 6 B: n = 1,000: 1,001,000 comparisons B: n = 2,000: 4,002,000 comparisons B: n = 100,000: about 10,000,100,000 comparisons, 17 minutes at 10^7 a second
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py
brute/chains.py
"""Helpers for chains of redirects: nodes with an address and the next node, which may loop back."""
class Node:
"""One redirect: an address and the next node (None after the last address)."""
def __init__(self, address, next=None):
self.address = address
self.next = next
def chain(addresses, loop_to=None):
"""Build a chain of nodes in order; with loop_to = i, the last node points back at node i. Returns the first node."""
nodes = [Node(a) for a in addresses]
for a, b in zip(nodes, nodes[1:]):
a.next = b
if loop_to is not None and nodes:
nodes[-1].next = nodes[loop_to]
return nodes[0] if nodes else None Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Doubling n quadruples both counts, and B alone would need about 17 minutes of CPython at its limit.
What to notice
In A, a walk that never ends is going round a loop, and two walkers at different speeds inside a loop must meet.
In B, read the list as a chain: from index i, go to index tickets[i]. No ticket is 0, so the walk from index 0
never returns to it and must end in a loop, and two indices point at the loop’s first index because they hold the
same number. B is A in disguise, which is the signal for fast and slow pointers.
A list of tickets read as a chain of indices
Text description of the diagram
The ticket list [2, 6, 4, 1, 3, 6, 5] is drawn as a chain: each index points at the index named by the ticket it holds.
Starting at index 0, which holds 2, the walk goes to index 2, which holds 4; then to index 4, which holds 3; to index 3, which holds 1; and to index 1, which holds 6. These five indices form the tail.
Index 1 points at index 6, which holds 5, and index 6 points at index 5, which holds 6 again, so index 5 points back at index 6. Indices 6 and 5 form the loop, and the walk goes round them for ever.
Index 6, where the loop starts, has a thick border. Two arrows point at it, one from index 1 and one from index 5, because both of those indices hold the ticket 6: the number that was issued twice.
The middle of a chain in one pass Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Floyd’s method, step by step
- Why the pointers must meet inside the loop
- The proof that phase 2 stops at the entry, with distances
- A dry run on the ticket list
- Why the loop’s first index is the repeated ticket
- The code in Python, JavaScript, Java, C++ and Go
- Brent’s method, and the function calls it saves, measured
- Edge cases and slips, caught by a stress test
- Variants, with answers
- Exercise: where a walk through an array starts to loop
- Find where a walk through an array starts to loop
- A practice set with answers
- Follow-up questions, answered
- Check yourself
- Interview questions
- If a linked list has a loop, why must a pointer that moves two nodes a step meet one that moves one node a step?
- An array holds n + 1 integers, each between 1 and n, and exactly one value repeats, perhaps more than twice. The array is read-only and you may use O(1) extra memory. Find the value, and compare your method with a binary search on counts.
- A link shortener stores redirects as database rows that point at the next row, and resolving a link costs one read per hop. A loop in the data made requests hang. How do you detect loops, how many reads does each method cost, and what do you change so that a request can never hang this way again?
- In Floyd's method the fast pointer moves two nodes a step. Would a fast pointer of three nodes still detect a loop? Would its second phase still find where the loop starts? Prove each answer or give a counterexample.
- Key takeaways
6 runnable examples (C++, Go, Java, JavaScript, Python, chains.mjs, chains.py, floyd.py) · 1 exercise with sample tests · 3 interview answers · about 26 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- An improved Monte Carlo factorization algorithm (R. P. Brent, BIT Numerical Mathematics 20, 1980) (Springer (BIT Numerical Mathematics))
- The Python Language Reference: identity comparisons (is) (Python Software Foundation)
- tracemalloc: trace memory allocations (Python Software Foundation)
- Strict equality (===) (MDN Web Docs)
- The Java Language Specification, Java SE 25: 15.21.3 Reference equality operators == and != (Oracle)
- The Go Programming Language Specification: comparison operators (The Go Authors)
- Equality operators (cppreference.com) (cppreference.com)
- AddressSanitizer (The LLVM Project)
- Fetch Standard: HTTP-redirect fetch (WHATWG)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress