Fast and Slow Pointers
Move one pointer twice as fast as the other to detect cycles, find the middle, or find where a loop starts, in O(1) space.
- O(n)Time: slow is caught within its first lap
- O(1)Extra space, against O(n) for a visited Set
- 1 and 2Steps per move for slow and fast
Show what to say
“When a linked list may have a cycle, or I need its middle, or a rule-defined sequence may repeat, and O(1) space is required, I run two pointers: slow moves one step, fast two. Fast laps slow inside a loop so they meet, and a second walk from the head finds where the loop starts.”
Trade-offs
- Floyd vs a visited Set: the Set is simpler and also O(n) time but costs O(n) memory; Floyd is O(1) space and answers the follow-up.
- Speed 2 vs 3: both meet, but only speed 2 makes the second walk land on the entry; with 3 it can miss.
- Second middle vs first middle: 'fast && fast.next' stops on the second on even lengths; 'fast.next && fast.next.next' gives the first, for cutting.
Bring it up before they ask
Check fast before fast.next in the loop condition (null.next throws), compare nodes rather than values, and remember the meeting point is inside the loop, not necessarily its entry.
Two runners on one list: slow takes one step, fast takes two. When fast runs out, slow is at the middle. In a loop, fast laps slow and they meet. All in O(1) extra space.
When to reach for it
Tap the signals you can see in the problem.
Rememberlinked listcyclemiddlesequence defined by a ruleO(1) extra space
- Find the middle
Pointersslow +1, fast +2
middle of the list - Cycle and its entry
Pointers+1 and +2 until they meet; then both +1
linked list cycle I and II - Implicit sequence
Pointers
happy numberfind the duplicatex → f(x)instead ofnode.next
The core idea: a faster runner laps a slower one
Two runners, one track. On a straight road the fast one finishes having run twice as far, so the slow one is half way: the middle.
Read more · 105 words
If the road ends in a loop, both circle it, and the fast one gains exactly one node per step. A gap that shrinks by one can't jump over zero, so they meet. Watch both phases, or build your own list with Edit input:
Why the second walk finds the entry. Let F be the steps from the head to the entry, and a how far past the entry they meet. Fast walked twice what slow did, so slow's steps are whole laps. Slow walked F + a, so F + a is whole laps, and F more steps from the meeting point land on the entry.
Floyd's cycle detection, then walk to the cycle's entry
- steps
- 0
Both pointers start on the head. The last node (7) links back to 5, but the code can't see that: it only follows next pointers.
function cycleStart(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
let entry = head;
while (entry !== slow) {
entry = entry.next;
slow = slow.next;
}
return entry;
}
}
return null;
}All steps as text
- Both pointers start on the head. The last node (7) links back to 5, but the code can't see that: it only follows next pointers.
- slow moves one node to 8; fast moves two, to 5.
- slow moves one node to 5; fast moves two, to 9.
- slow moves one node to 1; fast moves two, to 7.
- slow moves one node to 9; fast moves two, to 1.
- slow moves to 4 and fast jumps two nodes to 4: the same node.
- They meet 3 steps past the entry (a = 3). slow walked F + a = 2 + 3 = 5 steps and fast walked 10; the extra 5 is a whole number of laps of the loop (C = 5). So F = (whole laps) - a.
- Put entry on the head and leave slow on the meeting point. Both are now exactly F = 2 steps (plus whole laps) from where the loop begins.
- Both move one step: entry to 8, slow to 7. Still apart.
- Both move one step and land on 5 together, after 2 steps = F.
- The cycle begins at 5. Two pointers and two short walks: O(n) time, O(1) memory.
Play with it
Set the tail and the loop, press Run, and watch the hare lap the tortoise.
Read more · 62 words
Find the hidden list
What is
next?node.next, ori → nums[i].Pick the loop condition
while (fast && fast.next)keepsfast.next.nextsafe.Move, then compare
Both start on the head. Compare nodes (
slow === fast), never values.Decide what the meeting means
fasthit null: no cycle. A meeting: a cycle.Check the tiny cases
Empty list, one node.
Templates
Template 1: find the middle
The two loop conditions differ only on even lengths:
Read more · 46 words
| Loop condition | 1 → 2 → 3 → 4 → 5 | 1 → 2 → 3 → 4 → 5 → 6 | Use it for |
|---|---|---|---|
fast && fast.next | 3 | 4 (second middle) | the middle node |
fast.next && fast.next.next | 3 | 3 (first middle) | cutting the list |
Middle of the Linked ListEasy is this template; Palindrome Linked ListEasy starts with it.
Find the middle: fast moves two nodes per step
- steps
- 0
- slow at
- 1
- fast at
- 1
Both pointers start on the head. The list has 7 nodes, but the code never needs to know that.
function middleNode(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}All steps as text
- Both pointers start on the head. The list has 7 nodes, but the code never needs to know that.
- slow takes one step, to 2. After 1 step it has covered 1 node.
- fast takes two steps, to 3: it has covered 2 nodes, twice as many as slow.
- slow takes one step, to 3. After 2 steps it has covered 2 nodes.
- fast takes two steps, to 5: it has covered 4 nodes, twice as many as slow.
- slow takes one step, to 4. After 3 steps it has covered 3 nodes.
- fast takes two steps, to 7: it has covered 6 nodes, twice as many as slow.
- fast is on the last node (fast.next is null), so it can't take two more steps. Stop.
- fast covered the whole list while slow covered half of it, so slow is on the middle node, 4.
// Template 1: find the middle. fast moves two nodes for every one that slow moves,
// so slow is half way along when fast runs out of list.
export function middleNode(head) {
let slow = head;
let fast = head;
while (fast && fast.next) { // even length: slow ends on the second middle
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// Variant for splitting a non-empty list: the last node of the first half (the first middle on even lengths).
export function endOfFirstHalf(head) {
let slow = head;
let fast = head;
while (fast.next && fast.next.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}Template 2: detect a cycle and find its entry
Phase 1: move at speeds 1 and 2 until they meet or fast runs out.
Read more · 25 words
Phase 2: restart one at the head and step both by one until they meet at the entry.
Linked List CycleEasy needs phase 1; Linked List Cycle IIMedium needs both.
// Template 2: Floyd's cycle detection. Inside a loop, fast gains exactly one node per step on slow,
// so it can't jump over it: they meet. Without a loop, fast falls off the end.
export function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true; // the same node, not just an equal value
}
return false;
}
// Phase 2: the head and the meeting point are equally far from the cycle's entry (up to whole laps),
// so two pointers moving one step at a time from those two places meet exactly at the entry.
export function cycleStart(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
let entry = head;
while (entry !== slow) {
entry = entry.next;
slow = slow.next;
}
return entry;
}
}
return null;
}Template 3: cycles in implicit sequences
Floyd's algorithm only needs "the next one".
Read more · 62 words
Any rule f over a finite set makes a sequence that must repeat: a tail into a loop. Replace node.next with f(x) and the same two phases work.
- Happy NumberEasy:
f(x)is the digit-square sum;nis happy when the loop is1 → 1. - Find the Duplicate NumberMedium:
f(i) = nums[i]. The loop's entry has two incoming arrows: that is the duplicate.
Cycles without a list: x → f(x)
- slow
- 0
- fast
- 0
There are no nodes here: the "next" of x is f(x), read from the table. Following it from 0 gives 0 → 4 → 6 → 2 → 7 → 5 → 3 → 2 → …, which must repeat because there are only 8 possible values.
function floyd(f, x0) {
let slow = x0, fast = x0;
do {
slow = f(slow);
fast = f(f(fast));
} while (slow !== fast);
let entry = x0, tail = 0;
while (entry !== slow) {
entry = f(entry); slow = f(slow); tail++;
}
let length = 1;
for (let x = f(entry); x !== entry; x = f(x)) length++;
return { start: entry, tail, length };
}All steps as text
- There are no nodes here: the "next" of x is f(x), read from the table. Following it from 0 gives 0 → 4 → 6 → 2 → 7 → 5 → 3 → 2 → …, which must repeat because there are only 8 possible values.
- slow = f(slow) = 4; fast = f(f(fast)) = 6. Different values, keep going.
- slow = f(slow) = 6; fast = f(f(fast)) = 7. Different values, keep going.
- slow = f(slow) = 2; fast = f(f(fast)) = 3. Different values, keep going.
- slow = f(slow) = 7 and fast = f(f(fast)) = 7: they meet, so the sequence has entered its cycle.
- Same argument as for linked lists: x0 and the meeting point 7 are equally far from the start of the cycle. Restart entry at 0 and move both one step at a time.
- entry = 4, slow = 5. Not equal yet.
- entry = 6, slow = 3. Not equal yet.
- entry = 2 and slow = 2: they agree, so the cycle starts at 2, 3 steps after x0.
- Walk around the cycle to measure it: 2 → 7 so far, length 2.
- Walk around the cycle to measure it: 2 → 7 → 5 so far, length 3.
- Walk around the cycle to measure it: 2 → 7 → 5 → 3 so far, length 4.
- The sequence runs 3 steps into a cycle of length 4 that starts at 2. Happy Number and Find the Duplicate are exactly this, with f = digit-square sum and f(i) = nums[i].
// Template 3: a cycle in an implicit sequence x0, f(x0), f(f(x0)), … Nothing is stored: f plays the role of node.next.
// Returns where the repeating part starts, how many steps lead into it (tail) and how long it is.
export function floyd(f, x0) {
let slow = x0;
let fast = x0;
do { // phase 1: meet somewhere inside the cycle
slow = f(slow);
fast = f(f(fast));
} while (slow !== fast);
let entry = x0; // phase 2: equal distances lead to the start of the cycle
let tail = 0;
while (entry !== slow) {
entry = f(entry);
slow = f(slow);
tail++;
}
let length = 1; // phase 3: walk once around the cycle
for (let x = f(entry); x !== entry; x = f(x)) length++;
return { start: entry, tail, length };
}Try it
// Edit the inputs and press Run. fromArray(values, pos) links the tail back to index pos (-1: no cycle).
console.log(toArray(middleNode(fromArray([1, 2, 3, 4, 5])))); // [3, 4, 5]
console.log(toArray(middleNode(fromArray([1, 2, 3, 4, 5, 6])))); // [4, 5, 6]
const looped = fromArray([3, 8, 5, 1, 9, 4, 7], 2);
console.log(hasCycle(looped), cycleStart(looped).val); // true 5
console.log(hasCycle(fromArray([1, 2, 3]))); // false
const digitSquares = (n) => String(n).split('').reduce((sum, d) => sum + d * d, 0);
console.log(floyd(digitSquares, 19)); // { start: 1, tail: 4, length: 1 }: 19 is happy
console.log(floyd(digitSquares, 2)); // { start: 4, tail: 1, length: 8 }: 2 is notComplexity and JavaScript you'll use
Complexity
Read more · 71 words
| Variant | Time | Extra space |
|---|---|---|
| Find the middle | O(n) | O(1) |
| Detect a cycle and find its entry | O(n) | O(1) |
| Implicit sequence | O(tail + cycle length) | O(1) |
Visited Set, for comparison | O(n) | O(n) |
JavaScript you'll use
JavaScript refresher: Linked list and tree nodes=== on objects means the same node. Comparing .val does not.
while (fast && fast.next) { // check fast first: null.next throws
slow = slow.next;
fast = fast.next.next;
if (slow === fast) break; // === on objects compares identity: the same node
}
const seen = new Set(); // the O(n)-space alternative: seen.has(node)Interview questions
Why does fast move two steps? Would three work?
Three also meets, but only speed 2 makes phase 2 land on the entry. With 3 the second walk can miss.
How do you find the length of the cycle?
Hold one pointer, step the other around, count.
Visited Set or Floyd: which do you write first?
The Set: simpler, O(n) memory. Floyd answers the follow-up, "O(1) space?".
How do you find where two linked lists intersect?
Walk a pointer from each head; at the end, switch each to the other head. They meet there.
Can you check a linked list is a palindrome in O(1) space?
Find the middle, reverse the second half (list reversal), compare.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Middle of the Linked ListReturn the middle node of a singly linked list in one pass, choosing the second middle when the length is even.Easy
- Linked List CycleDecide whether following next pointers from the head ever loops back to a node you have already visited.Easy
- Happy NumberRepeatedly replace a number by the sum of the squares of its digits and decide whether it reaches 1 or loops forever.Easy
- Palindrome Linked ListDecide whether the values of a singly linked list read the same forwards and backwards, using O(1) extra space.Easy
- Linked List Cycle IIReturn the node where a linked list's cycle begins, or null if the list has no cycle, using O(1) extra space.Medium
- Find the Duplicate NumberAn array of n + 1 numbers from 1 to n has exactly one repeated value. Find it without modifying the array and with O(1) extra space.Medium
- Reorder ListRearrange a list in place so it alternates first, last, second, second-to-last, and so on.Medium