skip to content

In tortoise-and-hare cycle detection, why can't the fast reference skip past the slow one?

level: middleimportance: should knowfreq 60%

answer

  1. watch the gap, not the two positions
  2. how much does the gap change per step?
  3. relative speed is two minus one
  4. whole numbers count down through zero
  5. the loop is finite and closed

basics

~20 s

Inside the loop the fast reference gains exactly one node per step, so the gap between the two counts down one at a time and must pass through zero. Skipping over would require the gap to change by two or more.

solid answer

~50 s

Once both references are inside the cycle, stop tracking positions and track the gap — how far the fast reference is ahead of the slow one going forward around the loop, a value in `0..C-1` for a loop of `C` nodes. Each step the slow one moves one node and the fast one moves two, so the gap shrinks by exactly `2 - 1 = 1`. A whole number that starts below `C` and decreases by one each step must hit zero, and zero gap means both stand on the same node. Leapfrogging would need a jump of at least two, which the relative speed of one forbids. The slow reference enters the loop after the tail, and the countdown then takes fewer than `C` further steps, so detection is `O(L + C)` = `O(n)` with constant space.

code

pseudocode · 8 lines
pseudocode
slow = head
fast = head
while fast != END and fast.next != END:
    slow = slow.next
    fast = fast.next.next
    if slow == fast:
        return COLLISION_AT(slow)
return NO_CYCLE

go deeper

for a junior

Recall that the faster reference gains ground steadily rather than jumping, so the two must eventually land on the same node inside a loop. Knowing the conclusion is enough at this level.

for a middle

Give the gap argument explicitly: relative speed one, gap counts down through zero, collision within a lap of the slow reference entering. Also explain what the loop guard protects against on acyclic input.

for a senior

Demonstrate that you know which parts of the argument are load-bearing — that the bound is linear regardless of tail length, and that the two-to-one ratio matters for the follow-up step rather than for detection itself.

for a principal

Be able to judge when this reasoning is worth writing yourself versus using a well-tested chain-validation routine, and to insist that any hand-rolled variant carries tests for the acyclic boundary that the guard protects.

## The frame that makes the argument trivial Once **both** references are inside the loop, stop tracking their absolute positions and track only the **gap**: how many nodes the fast reference is ahead of the slow one, measured forward around the loop. Because the loop is finite and closed, that gap is a number in `0 .. C-1`, where `C` is the loop length. Each step, the slow reference advances one node and the fast one advances two. The gap therefore changes by `2 - 1 = 1` per step: **the fast reference closes on the slow one by exactly one node, every step.** A quantity that starts somewhere in `0 .. C-1` and decreases by exactly one each step must pass through zero. It cannot step from a gap of one to a gap of "minus one", because the next value after one *is* zero. Zero gap means both references are on the same node — a collision. This is the whole proof, and it is what the "could the fast one jump over the slow one?" objection misses. Skipping over would require the gap to change by two or more per step. It changes by exactly one, because the *relative* speed of a two-hop walker with respect to a one-hop walker is one hop. ## How long it takes The slow reference enters the loop after `L` steps, where `L` is the tail length. At that moment the fast reference is already somewhere in the loop, at some gap `d` with `0 <= d <= C-1`. The countdown then takes `d` more steps, so the collision happens **fewer than `C` steps after the slow reference enters the loop**. Total work is `O(L + C)`, which is `O(n)` — with a constant factor of about three node hops per element, since the fast reference covers twice the distance the slow one does. Note what the bound does *not* depend on: the payloads, the ordering, or any prior knowledge of `L`, `C`, or the total node count. The tail length decides *when* the countdown starts, not how long it lasts. ## Where the collision lands The collision node is *not* the loop entry in general. It is simply the node at which the countdown reached zero, which depends on both `L` and `C`. Treating the collision node as the entry is the single most common error in this area, and an interviewer will usually probe it immediately after you finish the meeting argument. ## Does the ratio have to be 1 and 2? Detection itself is robust: when both references start at the head, other speed pairs also collide. What is fragile is everything built *on top of* the collision. The neat "reset one reference to the head and advance both by one" trick for locating the loop entry depends on the fast reference having travelled exactly twice the slow one's distance; change the ratio and that arithmetic no longer holds in general, and you have to redo the algebra. A larger stride also burns more link traversals per step for no asymptotic gain, and complicates the end-of-list guard (a three-hop walker must check three links). One and two is the pair worth memorising, and "because the relative speed is exactly one" is the reason worth being able to say. ## The guard, and why it is not decoration ``` while fast != END and fast.next != END: slow = slow.next fast = fast.next.next ``` The double hop dereferences two links. On an acyclic list, either the fast reference itself or its immediate successor can be the end, so both must be tested before the hop. Testing only the first is a boundary bug that shows up exactly on the *acyclic* inputs — the ones a quick test with a hand-built cycle never exercises. Inside a genuine cycle neither test ever fires, which is precisely why the loop runs until the collision instead of terminating. ## Saying it in an interview "Once both are in the loop, look at the gap between them. The fast one gains one node per step, so the gap counts down one at a time and has to hit zero — it can't leapfrog, because leapfrogging would need a jump of two. The slow one enters the loop after the tail, and from there it takes fewer than a full lap, so the whole thing is linear in the number of nodes with constant extra space." That is thirty seconds, and it answers the question that was actually asked.

  • At most how many further steps after the slow reference enters a loop of length C do the two collide?
    Fewer than `C`. When the slow reference enters, the gap is some value between `0` and `C-1`, and it shrinks by exactly one per step, so the countdown finishes in under a full lap. Combined with the `L` steps to traverse the tail, total work is `O(L + C)`, which is linear in the number of nodes.
  • Why does the loop guard test both the fast reference and its successor before the double hop?
    The double hop dereferences two links, and on an acyclic chain either the fast reference itself or its immediate successor can be the end marker. Testing only the first walks off the end. The bug fires exactly on acyclic input, which is why a quick test using only a hand-built cyclic list never catches it.
  • If the fast reference took three hops per step instead of two, what would break?
    Detection still works when both start at the head, but the second phase does not. Resetting one reference to the head and advancing both singly relies on the fast one having covered exactly twice the slow one's distance; with a three-hop stride the arithmetic gives a different relation and the reset no longer lands on the loop entry in general.

Two runners on a circular track, one lapping at exactly one lane-length per lap faster than the other: the distance between them shortens by one fixed unit each lap and therefore reaches zero rather than jumping over it.

saying these in an interview costs you the question

  • Claims the fast reference could jump over the slow one
  • Says the two must start at different nodes to work
  • Thinks the meeting depends on the loop length being even
  • Says collision time grows with the tail length
  • Believes any speed pair preserves the entry-finding step

context