Fast and Slow Pointers
Some problems hand you a sequence with no fixed end — a linked list that
might loop back on itself, or a number sequence defined by “apply this
function again.” You can’t just check if visited without extra memory.
Fast and slow pointers (Floyd’s Tortoise and Hare) solve this with two
pointers moving through the same structure at different speeds — no
extra memory, no hash set.
What you’ll learn
Section titled “What you’ll learn”- Floyd’s Tortoise and Hare: why a fast pointer laps a slow one inside a cycle, guaranteeing they meet.
- Cycle detection, and the follow-up trick that finds where the cycle starts.
- Finding the middle of a linked list in one pass.
- The Happy Number problem — the exact same pattern, no linked list in sight.
- The cue: “does this ever repeat/loop” without room for a visited set.
The cue
Section titled “The cue”After slow and fast meet inside the cycle, reset one pointer to the
head and advance both one step at a time — they meet again exactly at
the cycle’s start. This works because of the distance math: the distance
from the head to the cycle start equals the distance from the meeting
point back around to the cycle start (Floyd’s second phase).
Dry run
Section titled “Dry run”LC 142 — [3, 2, 0, -4] with the tail linking back to index 1 (the node holding 2).
Phase 1, find a meeting point. Both start at the head:
| step | slow | fast | met? |
|---|---|---|---|
| 1 | 2 | 0 | no |
| 2 | 0 | 2 | no |
| 3 | −4 | −4 | yes |
Phase 2, find the entrance. Reset one pointer to the head and advance both one step at a time:
| step | from head | from meeting point | same? |
|---|---|---|---|
| 1 | 2 | 2 | yes → the cycle starts at the node holding 2 |
- The meeting point is not the cycle entrance. They met at −4; the entrance is 2. Returning the meeting node is the standard LC 142 bug, and it passes LC 141 (which only asks whether a cycle exists), so it can survive being written and tested.
- Why phase 2 works, in one line of algebra. Let
abe the distance from head to entrance andbthe distance from entrance to the meeting point. When they meet, slow has walkeda + band fast2(a + b), and fast’s extraa + bmust be a whole number of laps. Soa + bis a multiple of the cycle length, which means walkingamore steps from the meeting point lands exactly on the entrance — and walkingasteps from the head does too. That is why both pointers move at the same speed in phase 2. - Fast cannot jump over slow. It closes the gap by exactly one node per step, so once slow is inside the cycle the meeting is guaranteed within one lap. This is also why the 2× speed specifically matters: a 3× runner can leap past.
- The loop guard is
while fast and fast.next. Both checks are needed:fast.next.nexton a list of even length would otherwise dereferenceNone.
Finding the middle — [1,2,3,4,5]: slow ends on 3 after fast runs out. On even length
[1,2,3,4], while fast and fast.next leaves slow on 3 (the second middle) while
while fast.next and fast.next.next leaves it on 2 (the first). Which you want is
problem-specific — reorder and palindrome need the first middle so the split favours the left
half.
Time and space complexity
Section titled “Time and space complexity”| Operation | Time | Space |
|---|---|---|
| Cycle detection (hash-set of visited nodes) | ||
| Cycle detection (fast/slow pointers) | ||
| Find middle / cycle start (fast/slow) |
The whole point of this pattern is trading the hash set’s memory for — same time complexity, none of the extra space.
When to use it
Section titled “When to use it”| Cue in the problem | Why fast/slow fits |
|---|---|
| “does this linked list have a cycle” | Direct application |
| “find the start of the cycle” | Meet, then reset + walk together |
| “find the middle of a linked list” | fast reaches end when slow is at middle |
| “does this number sequence eventually repeat/reach 1” | Happy Number-style, no list needed |
| “find the duplicate number without extra space” | Treat array values as a linked list via indices |
The variant map
Section titled “The variant map”| Problem | Speeds / gap | What you read off |
|---|---|---|
| LC 141 Cycle detection | 1 and 2 | whether they ever meet |
| LC 142 Cycle start | 1 and 2, then 1 and 1 from head and meeting point | the entrance node |
| Cycle length | after meeting, keep one still and walk the other | steps taken to return |
| LC 876 Middle of the list | 1 and 2 | slow when fast runs out; the loop condition picks which middle |
LC 19 Remove n-th from end | same speed, gap of n | the node before the target, if the gap opens from a dummy |
| LC 234 Palindrome list | 1 and 2 to the middle, then reverse and compare | equality in space |
| LC 143 Reorder list | 1 and 2 to split | see Copy Flatten and Reorder |
| LC 202 Happy Number | 1 and 2 over digit-square-sums | there is no list at all — the successor function is arithmetic |
| LC 287 Find the Duplicate | 1 and 2 over i → nums[i] | the duplicate is the cycle entrance, so phase 2 is the answer |
| LC 457 Circular Array Loop | 1 and 2 with direction and self-loop guards | a valid cycle of length > 1 |
| Any functional graph | 1 and 2 | Floyd’s applies to any “each state has exactly one successor” |
Interview follow-ups
Section titled “Interview follow-ups”| They ask | What they’re checking | The answer |
|---|---|---|
| “Why does the fast pointer always catch the slow one?” | Whether you can prove it | Once slow is inside the cycle, fast closes the gap by exactly one node per step, so it cannot jump over — the meeting happens within one lap. Speeds 1 and 2 are what guarantee that; a 3× runner can leap past |
| “You found where they meet. How do you find the cycle’s start?” | The part people skip | Reset one pointer to the head and advance both at one step. Where they meet again is the entrance. It works because a + b is a whole number of laps, so a more steps from the meeting point lands on the entrance |
| “What is the complexity?” | Precision | time, space. The space alternative is a visited set — mention it, because it is clearer and the interview is asking you to beat it |
| “How long is the cycle?” | Whether you can extend it | After they meet, hold one pointer still and walk the other until it returns. The number of steps is the cycle length |
| “There is no linked list — the input is an array” (LC 287) | Recognising the abstraction | Treat i → nums[i] as edges. Values in 1..n with n+1 slots means two indices point to the same place, so the functional graph has a cycle and the duplicate is its entrance — phase 2 returns the answer directly |
| “Now the graph has nodes with two successors” | The boundary | Floyd’s requires a functional graph. With branching you need DFS with three colours or a visited set; there is no two-pointer equivalent |
| “Find the middle — which one on even length?” | Attention to the loop condition | while fast and fast.next gives the second middle; while fast.next and fast.next.next gives the first. Reorder and palindrome want the first, so the split favours the left half |
| “Remove the n-th node from the end in one pass” | The gap variant | Open a gap of n between two pointers starting from a dummy, then advance both. When the leader falls off, the trailer sits just before the target — which is the only position from which it can be unlinked |
Practice — real LeetCode problems
Section titled “Practice — real LeetCode problems”Each exercise is the actual LeetCode problem with its real method signature and LeetCode’s own examples as the test. Write the body, press Run, and match the expected output.
LC 141 — Linked List Cycle · Easy
Section titled “LC 141 — Linked List Cycle · Easy”Problem. Return True if the linked list contains a cycle.
Constraints. 0 <= number of nodes <= 10^4. Can you do it in memory?
Examples. [3,2,0,-4] with the tail linked to index 1 gives True ·
[1] with no cycle gives False
Editorial
If a cycle exists, the fast pointer enters it first and then gains one position on the slow pointer per step, so the gap shrinks by one each time and must reach zero — they collide. Without a cycle, fast reaches the end.
Time . Space — which is the whole point, since a visited set
solves it trivially at space.
Two details:
while fast and fast.next. Both are needed, because the step isfast.next.next. Checking onlyfastraisesAttributeErroron the last node.slow is fast, notslow.val == fast.val. Values may repeat, so value equality reports false cycles. The habit matters even though this problem’s tests may not punish it.
Follow-ups: “Find where the cycle starts (LC 142)?” — next problem. “Cycle length?” — once they meet, keep one still and walk the other until it returns. “Why must they meet rather than skip past?” — the gap decreases by exactly one per step, so it cannot jump over zero. “Faster than 2x speed?” — still works, but the proof is less clean and there is no benefit.
LC 142 — Linked List Cycle II · Medium
Section titled “LC 142 — Linked List Cycle II · Medium”Problem. Return the node where the cycle begins, or None if there is no
cycle. Do not modify the list.
Constraints. 0 <= number of nodes <= 10^4. Can you do it in memory?
Examples. [3,2,0,-4] with the tail linked to index 1 returns the node at
index 1 · [1] with no cycle returns None
The problem returns a node, so the test checks the returned node is the actual cycle-entry node object — not merely one with a matching value.
Editorial
Phase 1 finds a meeting point; phase 2 converts it into the cycle entrance.
Time . Space .
Why phase 2 works. Let L be the distance from the head to the cycle entrance,
C the cycle length, and m the distance from the entrance to the meeting point.
When they meet, slow has travelled L + m and fast 2(L + m), and their difference
must be a whole number of laps: L + m = nC. So L = nC - m — meaning walking L
steps from the head, and L steps forward from the meeting point, both land on the
entrance. Hence stepping both pointers one at a time from those two positions makes
them meet exactly there.
Being able to give that argument is the point of the problem; the code is six lines.
The test compares with is because the problem specifies returning the node. A
solution that finds the right position but constructs a new node would fail, correctly.
Follow-ups: “Prove phase 2” — the arithmetic above; the expected question.
“Cycle length?” — walk from the meeting point back to itself. “With a visited
set?” — space, trivial, and the reason the version is asked for.
“Where else does this apply?” — LC 287 below, and
Happy Number (LC 202), where the “list” is a function.
LC 287 — Find the Duplicate Number · Medium
Section titled “LC 287 — Find the Duplicate Number · Medium”Problem. An array of n + 1 integers, each in [1, n], contains exactly one
repeated number. Return it without modifying the array and using only
extra space.
Constraints. 1 <= n <= 10^5, values in [1, n], exactly one value repeats
(possibly many times).
Examples. [1,3,4,2,2] gives 2 · [3,1,3,4,2] gives 3 ·
[3,3,3,3,3] gives 3
Editorial
The reframing is everything: read the array as a function i -> nums[i], giving a
linked structure. Since every value is in [1, n] but there are n + 1 indices, two
indices must point at the same place — so the structure has a cycle, and the
duplicated value is precisely the node where two arrows converge, i.e. the cycle
entrance.
Then it is LC 142 applied to indices instead of nodes.
Time . Space , and the array is untouched — which is exactly what the constraints demand.
Why the obvious solutions are excluded: a set is space; sorting modifies the
array (and is ); the
cyclic-sort marking trick
also modifies it. Those constraints exist to force this insight.
Starting at nums[0] rather than index 0 matters: because values are at least 1,
index 0 is never a target, so it cannot be inside the cycle — which makes it a valid
“head” outside the loop.
[3,3,3,3,3] giving 3 is the many-repeats case, and [1,1] is the minimum size.
Follow-ups: “Prove there must be a cycle” — pigeonhole on n + 1 indices into n
values. “Binary search on the value range?” — also space: count how many
values are <= mid and compare against mid; but easier to explain.
“If modification were allowed?” — mark visited slots by negation, . “Multiple
duplicates?” — Floyd’s finds one; you would need a different approach for all.
LeetCode problem set
Section titled “LeetCode problem set”Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.
Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.
- 141Linked List CycleeasyThe base template: if the pointers ever meet, there is a cycle
- 202Happy NumbereasyThe 'linked list' is the digit-square function -- same cycle detection, no nodes involved
- 876Middle of the Linked ListeasySlow lands on the middle exactly when fast runs off the end
- 142Linked List Cycle IImediumOnce they meet, reset one pointer to the head and advance both by one -- they meet at the entrance
- 287Find the Duplicate NumbermediumTreat `i -> nums[i]` as a linked list; the duplicate value is the cycle entrance
Pitfalls
Section titled “Pitfalls”- Returning the meeting node as the cycle start. They meet somewhere inside the loop, not at its entrance. Phase 2 — reset to head, walk both at speed 1 — is what finds the entrance, and omitting it still passes LC 141.
- Advancing at 2× in phase 2. Both pointers move one step there. Keeping fast at 2× gives a node that is usually wrong and occasionally right, which is the worst kind of bug.
while fast and fast.nextversuswhile fast. Checking onlyfastdereferencesNoneonfast.next.nextfor even-length lists.- Comparing values instead of identity.
slow.val == fast.valis true for any duplicate value; cycle detection needsslow is fast. - Starting the pointers apart. Both must begin at the head for the
a + balgebra to hold. Seedingfast = head.nextis a common “optimisation” that breaks phase 2. - The wrong middle. On even-length input the two loop conditions give different nodes; pick deliberately, because reorder and palindrome need the first middle.
- Assuming it works on a branching graph. Floyd’s needs exactly one successor per node.
- Forgetting the dummy in LC 19. Opening the gap from the head instead means removing the head itself needs a special case.
Self-check
Section titled “Self-check”-
Why is the fast pointer guaranteed to catch the slow one inside a cycle?
The 2× speed is what makes the gap shrink by exactly 1. A 3× runner shrinks it by 2 and can leap past the slow pointer entirely.
pch.quizShowAnswer
B — Because it closes the gap by exactly one node per step, so it cannot jump over — the meeting happens within one lap — The 2× speed is what makes the gap shrink by exactly 1. A 3× runner shrinks it by 2 and can leap past the slow pointer entirely.
-
You have the meeting node. How do you find where the cycle STARTS?
In the dry run they meet at −4 but the cycle starts at 2. Returning the meeting node still passes LC 141, which only asks whether a cycle exists — so the bug can survive testing.
pch.quizShowAnswer
B — Reset one pointer to the head and advance both at ONE step each — where they meet again is the entrance — In the dry run they meet at −4 but the cycle starts at 2. Returning the meeting node still passes LC 141, which only asks whether a cycle exists — so the bug can survive testing.
-
Why does phase 2 work?
That one line of algebra is also why both pointers must move at the SAME speed in phase 2 — keeping fast at 2× breaks the equality.
pch.quizShowAnswer
B — Because when they meet, slow has walked a + b and fast 2(a + b), so fast's extra a + b is a whole number of laps — meaning a more steps from the meeting point lands on the entrance, exactly as a steps from the head does — That one line of algebra is also why both pointers must move at the SAME speed in phase 2 — keeping fast at 2× breaks the equality.
-
LC 287 asks for a duplicate in an array of n+1 values from 1..n, in O(1) space. How is that this pattern?
Recognising a functional graph where no list exists is what this pattern really teaches. LC 202 (Happy Number) is the same move with an arithmetic successor function.
pch.quizShowAnswer
B — Treat i → nums[i] as edges: two indices pointing at the same value means the functional graph has a cycle, and the duplicate IS the cycle's entrance — so phase 2 returns it directly — Recognising a functional graph where no list exists is what this pattern really teaches. LC 202 (Happy Number) is the same move with an arithmetic successor function.
-
On an even-length list, which node does `while fast and fast.next` leave `slow` on?
On [1,2,3,4] the two conditions give 3 and 2 respectively. Choosing by accident is how the split ends up favouring the wrong half.
pch.quizShowAnswer
B — The second middle — use `while fast.next and fast.next.next` if you need the first, as reorder and palindrome do — On [1,2,3,4] the two conditions give 3 and 2 respectively. Choosing by accident is how the split ends up favouring the wrong half.
-
The nodes can now have two successors each. Does Floyd's still apply?
Knowing the precondition is what stops you reaching for a two-pointer trick on a general graph, where there is no equivalent.
pch.quizShowAnswer
B — No — Floyd's requires a functional graph (exactly one successor per node). With branching you need DFS with three colours, or a visited set — Knowing the precondition is what stops you reaching for a two-pointer trick on a general graph, where there is no equivalent.
Recall card
Section titled “Recall card”- Cue — cycles, middles, or
n-th-from-end in one pass with space; or any functional graph (each state has exactly one successor). - Detect —
slow = slow.next,fast = fast.next.next, guardwhile fast and fast.next, compare withisnot==. - Entrance (LC 142) — after they meet, reset one to the head and advance both by one. They
meet at the entrance because
a + bis a whole number of laps. - Cycle length — hold one still after meeting, walk the other back round.
- Middle —
slowwhenfastruns out; the loop condition decides first-versus-second middle on even lengths. n-th from end — same speed, gap ofn, opened from a dummy.- No list required — LC 202 (digit squares) and LC 287 (
i → nums[i]) are the same pattern with a different successor function. - Cost — time, space. The -space
visitedset is the baseline you are being asked to beat. - Precondition — exactly one successor per node. Branching graphs need DFS instead.
- Fast and slow pointers detect cycles in time and space — no hash set of visited nodes required.
- The same two-speed idea finds a linked list’s middle (fast finishes when slow is halfway) and, with a second phase, the cycle’s start.
- The pattern isn’t limited to linked lists — any “repeatedly apply a function” sequence (Happy Number, Find the Duplicate Number) fits too.
- Cue: “does this ever loop/repeat” with no room for extra memory.
Next: Merge Intervals — sorting overlapping ranges by start time and collapsing them into a minimal set.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading