The Idea: Two Walkers, Different Speeds
A linked list hides a brutal asymmetry: you can walk forward in per step, but you cannot index, you cannot look back, and you cannot tell from a node alone whether it sits on a list, on a loop, or near the end. The fast-slow pointer technique — also called the two-pointer trick or Floyd's tortoise and hare — is the most elegant antidote in our toolbox. It says: walk the same list with two pointers at two different speeds, and their relative positions become a measuring device for structure you cannot see directly.
The canonical pair is “slow advances by 1, fast advances by 2.” Concretely, in Python:
1slow = head
2fast = head
3while fast is not None and fast.next is not None:
4 slow = slow.next # +1 step
5 fast = fast.next.next # +2 stepsThat tiny loop is the basis for at least five separate algorithms. Each one reads off a different invariant of the same simulation:
| Question | What to read off the simulation |
|---|---|
| Where is the middle node? | When fast falls off the end, slow is at floor(n/2). |
| Is there a cycle? | If fast ever equals slow inside the loop, yes; if fast hits None, no. |
| Where does the cycle start? | After they meet, reset slow to head; advance both by 1; they meet at the entrance. |
| What is the k-th from the end? | Run the lead pointer k steps first, then walk both at speed 1. |
| Does this number-theoretic sequence loop? | Apply the same idea to f(n) = sum of squared digits. |
Why is this loop so cheap?
Application 1 — Find the Middle of a List
Given a singly linked list of nodes, return the middle node in one pass without computing the length first. The fast-slow recipe makes this almost trivial.
The Invariant
Whenever fast has advanced steps from the head, slow has advanced exactly . So when fast falls off the end after total steps, slow has taken steps and is therefore at the middle (rounded down).
Two flavours appear in interview questions, depending on which middle you want for an even-length list:
| Loop guard | n=6 → returns | n=7 → returns | Used in |
|---|---|---|---|
| while fast and fast.next: | node 3 (the second middle) | node 3 (the unique middle) | Reorder list, palindrome check (right half) |
| while fast.next and fast.next.next: | node 2 (the first middle) | node 3 (the unique middle) | Splitting for merge sort (left half stays one bigger or equal) |
Pick a flavour and stick with it
Application 2 — Detect a Cycle (Floyd's Algorithm)
Robert W. Floyd popularised this algorithm in the late 1960s; it has been rediscovered countless times because the proof is genuinely surprising the first time you see it.
Setup. A list of nodes either ends in None (no cycle) or loops back to some earlier node (a cycle). Let
- = distance from the head to the cycle entrance (so if the head itself is on the cycle), and
- = length of the cycle.
Claim. If a cycle exists, slow and fast meet inside it within total slow-steps. If no cycle exists, fast hits None in slow-steps.
The Catch-Up Argument
Once both pointers are inside the cycle, fast gains exactly one node per step on slow (fast moves 2, slow moves 1, net ). Modulo , the gap between them decreases by 1 each step. A non-negative integer that strictly decreases must hit 0, and once it does, slow == fast: collision detected. So they collide within at most steps after both are inside.
Both being inside takes at most slow-steps (slow first reaches the entrance after exactly steps; fast is already on the cycle by then because it moves twice as fast). Total work: .
The litmus test
Application 3 — Find the Cycle Entrance
Floyd's real wizardry isn't cycle detection; it's the second phase. Once slow and fast meet, you can locate the exact node where the cycle begins, in time and memory, by walking two pointers at speed 1.
The Algorithm
- Run phase 1 until slow == fast (the meeting point). Call it
M. - Reset slow to
head; leave fast atM. - Advance both by exactly 1 step at a time.
- The first node where they coincide is the cycle entrance. (If the head itself was on the cycle, they coincide immediately at
head.)
Why It Works — A Real Proof
Pick the meeting point M. Let be the distance from the cycle entrance to M measured along the direction of travel (so ).
At the moment of collision, slow has taken steps. Fast has taken twice as many, , but fast also went around the cycle some integer number of extra times. So fast's actual path length is . Setting both expressions for fast's path equal:
Read this in words: the distance from head to the entrance equals the distance from M to the entrance, modulo . So if you start one walker at head and another at M and step both forward by 1, they will both reach the entrance simultaneously after steps — the head walker by definition, and the M walker because it covers steps within the cycle, landing at the entrance.
Geometric reading
Phase 2 must use speed 1, not speed 2
fast.next.next trick in phase 2. Don't. The proof depends on both walkers covering the same number of edges. Speed 2 in phase 2 would land on the wrong node.Application 4 — k-th Node from the End
Same machinery, different speeds. To find the -th node from the tail in one pass, use a fixed-gap two-pointer walk:
- Place
leadandtrailboth athead. - Advance
leadby exactly steps. (If it walks off the list, the input has fewer than nodes — raise.) - Now walk both pointers at speed 1 until
leadis null. At that momenttrailis the -th from the end.
The invariant: lead is always exactly steps ahead of trail. So when lead reaches null, trail is nodes behind — which is precisely the definition of “k-th from end.”
This is structurally the same algorithm as middle-finding, with two changes: the gap is instead of dynamic, and we walk both pointers at speed 1 instead of mismatched speeds. That common skeleton is what makes “two pointers” a single technique rather than five different ones.
Application 5 — Happy Numbers and Functional Iteration
Here is a stunning use of fast-slow that has nothing to do with linked lists. A positive integer is happy if iterating “replace it with the sum of squares of its digits” eventually reaches 1; it is unhappy if the sequence enters a cycle that does not contain 1.
Example for : . Happy.
Example for : . Cycle. Unhappy.
The trick: define = sum of squared digits of . The sequence is a linked list of integers where node.next is . Floyd's algorithm tells us whether this list cycles, and the only fixed point is , so “reaches 1” is equivalent to “does not enter a non-1 cycle.”
The general principle
Interactive: Tortoise & Hare Lab
Pick a topology, set the cycle position with the slider, and step the simulation by hand. Watch slow (green S) and fast (red F) collide inside the loop; then press start phase 2 to launch the head walker (blue H) and see the proof come alive: head and slow each walk one step, and they meet exactly at the cycle entrance.
Tortoise & Hare Lab — slow walks 1, fast walks 2
steps = 0phase: walkTry a few experiments to internalise what the variables mean:
- Set (cycle start = 0). Slow and fast meet, and phase 2 immediately reports the entrance as the head — no extra walking needed.
- Make the stem long and the cycle short (e.g. , cycle start = 9). Phase 1 spends most of its time in the stem; phase 2 walks the long way.
- Switch to the straight topology and watch fast outrun slow. When fast hits None, slow is at — that is the middle-finding algorithm.
Reference Implementation in Python
These four routines together cover every flavour of fast-slow you will meet in interviews and production code. Read them as variations on the same loop.
Find the Middle
Detect a Cycle
Find the Cycle Entrance
k-th Node from the End
Same Routines in TypeScript
Here is the same suite ported to TypeScript. The translation is mechanical, but two language-specific details deserve attention: nullability is in the type (we get compile-time help we did not have in Python), and identity vs. equality is unambiguous because === on objects compares references.
Brent's Algorithm — A Faster Cycle Detector
Richard P. Brent (1980) gave a different cycle detection scheme that is asymptotically the same as Floyd's but with smaller constants in practice. The idea: instead of walking both pointers every step, keep slow stationary for fast-steps, then teleport slow to fast and double the budget.
1def has_cycle_brent(head):
2 if head is None or head.next is None:
3 return False
4 slow = head
5 fast = head.next
6 power = 1
7 length = 1
8 while slow is not fast:
9 if fast is None or fast.next is None:
10 return False
11 if length == power:
12 slow = fast
13 power *= 2
14 length = 0
15 fast = fast.next
16 length += 1
17 return TrueWhy is this faster in practice? Floyd does two pointer dereferences per step (slow + fast); Brent does one (only fast moves). When pointer chasing is the bottleneck (almost always, on real hardware where each node.next is a cache miss), halving the dereferences halves the wall-clock time. Brent also discovers , the cycle length, as a free side effect — useful for Pollard's rho.
When to use which
Where Fast-Slow Pointers Run in Production
Operating Systems and Memory
- Garbage collectors use cycle detection variants when freeing reference-counted objects: a cycle of references would otherwise leak forever. CPython's
gcmodule is the canonical example, though it uses a different (graph-based) algorithm; embedded reference-counting allocators in C++ smart-pointer libraries use Floyd-style detection on suspicious sub-graphs. - Memory leak detectors in Linux kernel modules walk allocator linked lists and apply Floyd to detect circular corruption from buggy drivers.
- Filesystem fsck tools use cycle detection when verifying that the directory tree is actually a tree (no cycles via misplaced hard links on systems where directory hard-linking is permitted).
Compilers and Language Runtimes
- Module loaders (Python's import system, Node's require, Java's class loaders) detect circular imports during dependency resolution. The classical approach uses an explicit visited set, but in low-memory bootloaders the fast-slow trick gets used on the “currently resolving” stack.
- Type-inference engines for languages with structural types must detect cycles in type-alias chains:
type A = B; type B = C; type C = A;— a cycle Floyd's detects in memory.
Networking and Distributed Systems
- Routing protocols use TTL fields as a poor-man's cycle bound, but on the control plane, link-state databases run cycle detection over forwarding tables to catch routing loops.
- Distributed-deadlock detection: the wait-for graph among lock holders forms a directed structure where a cycle = deadlock. Banker's and Chandy-Misra-Haas algorithms use cycle-detection primitives related to fast-slow.
Cryptography and Number Theory
- Pollard's rho factorization — the most famous application. To factor an integer , iterate and run Floyd's on the sequence. When you find a collision, often gives a non-trivial factor. Expected time by the birthday paradox.
- Pollard's rho for discrete logarithms uses the same skeleton in cryptanalysis of weak elliptic-curve parameters.
- Pseudorandom-number-generator validation: any deterministic PRNG over a finite state space must eventually cycle; Floyd or Brent detects the period. Engineers running statistical simulations check this to ensure their generator's period exceeds the simulation length.
Algorithms Interviews and Competitive Programming
- Find the Duplicate Number (Leetcode 287). Given an array of n+1 integers in [1, n] with one duplicate, use values as pointers (
i → nums[i]); the duplicate is the entrance of a cycle. time, memory — a result that astonished interviewers when first popularised. - Linked List Reorder, Palindrome Check, Rotate by k — all rely on middle-finding via fast-slow as a subroutine.
- Intersection of Two Linked Lists uses a related two-pointer trick: walk both, switch heads when one runs out, and you are guaranteed to meet at the intersection (or both at None) after at most steps.
Robotics, AI, and Everyday Software
- State-machine cycle detection in autonomous-driving planners: a planner exploring possible state sequences must avoid revisiting equivalent states; Floyd's runs on the canonical-state hash chain.
- Spreadsheet circular-reference detection: every time you type
=A1into cell A1, Excel runs a cycle check on the dependency graph — a problem with elegant fast-slow flavour when the graph is sparse. - Database query planners: view-resolution traversal must detect circular view definitions (view A references view B references view A), often via Floyd-style detection during the planner's dependency walk.
Pitfalls and How to Avoid Them
- NullPointer on
fast.next.next. Always guard with bothfast is not NoneANDfast.next is not None. Dropping the second check kills the program on any odd-length list at the last iteration. - Off-by-one in middle-finding.
while fast and fast.nextvs.while fast.next and fast.next.nextgive different middles for even . Pick one and document it. - Using == instead of
is(or===in TS). Identity, not equality, is what cycle detection requires. Two distinct nodes can carry equal values. - Phase 2 with speed 2. The cycle-entrance proof requires both pointers walking by 1 in phase 2. Speed 2 lands on the wrong node.
- Resetting the wrong pointer. In phase 2, reset slow to head; do not reset fast. Fast must stay at the meeting point M for the math to work.
- Forgetting case. If the head itself is on the cycle, phase 2 must return head immediately. Both reference implementations above handle this because the
while slow is not fastguard checks before stepping. - k-th-from-end with k = list length. The sprint loop walks lead off the tail and into None exactly when k equals the length; the lockstep loop then runs zero times and returns head — correct. Test this boundary explicitly.
- Mutating during traversal. If you delete or splice nodes while a cycle-detection pass is in flight, all bets are off. Either snapshot a list of
(prev, curr)pairs and operate on it afterwards, or hold a lock.
Quick Checks
Quick Check
On a 6-node list 1→2→3→4→5→6→None with the standard `while fast and fast.next:` loop guard, where is slow when fast first becomes None?
Quick Check
A list has a cycle that begins at the head itself (μ = 0, λ = 5). After phase 1 of Floyd's algorithm, slow and fast meet. What does phase 2 do before returning the cycle entrance?
Fast-slow pointers are the kind of trick you only need to understand once. Internalise the catch-up argument and the identity, and a whole family of problems — middle, cycle, entrance, k-from-end, happy numbers, Pollard's rho — collapses into one short loop. In the next section we will look at “When to Use Linked Lists vs Arrays” and revisit why the techniques that make linked lists tolerable do not change the underlying cache-locality verdict.