skip to content

questions

4

What must the step function f guarantee for tortoise-and-hare detection on x = f(x) to be correct?

level: middleimportance: must knowfreq 62%

answer

  1. Ask what the fast pointer has to assume
  2. Same input, same successor, no side effects
  3. How many arrows leave each state?
  4. What if a state is terminal?
  5. Three step evaluations per iteration

basics

~20 s

The step must be deterministic and side-effect-free, must give every reachable state exactly one successor over a finite set, and must be cheap to re-evaluate. Branching states, or a walk that can simply end, invalidate the two-pointer loop.

solid answer

~40 s

Four requirements. **Deterministic and pure**: the successor depends only on the current value, so the fast pointer can recompute the same walk twice as quickly. **Total on the reachable set**: every state has exactly one successor, which is what makes the sequence an implicit chain rather than a graph; if a state can end the walk, the loop needs an explicit termination check before advancing. **Finite reachable set**, so a cycle is guaranteed to exist rather than merely possible. **Cheap to evaluate**, because the pattern calls the step roughly three times per element and never caches anything. Branching is the requirement people miss: a state with two outgoing transitions is a general graph, and relative motion of two pointers proves nothing there.

code

pseudocode · 11 lines
pseudocode
slow = start
fast = start
while true:
    slow = f(slow)
    fast = f(f(fast))
    if slow == fast:
        return "cycle"
// if a state can be terminal, the fast pointer must be
// guarded before each of its two hops, and the routine
// returns "no cycle" when it runs off the end
...

go deeper

for a junior

Recall the core precondition in one line: every state must have exactly one next state, computed the same way every time. Without that, two pointers are not walking a single chain.

for a middle

Explain each requirement and what fails without it — branching, terminal states, side effects, cost — and be able to point at where a termination guard belongs in the bare loop.

for a senior

Show the modelling judgment: given a real transition system, argue whether it is genuinely single-successor and bounded before proposing the trick, and state plainly what a pointer meeting does and does not prove.

for a principal

Own the design angle — a transition rule kept pure, total and bounded buys constant-space loop detection across every workflow in the fleet, while one branching or side-effecting step forces every consumer into a memory-hungry alternative.

## The setting Picture a workflow engine for deployments. Every stage — build, canary, soak, promote, roll back — has exactly one outgoing transition determined by the stage's own recorded outcome. A deployment has been sitting in the pipeline for six hours and someone asks: is it making progress, or is it looping between roll-back and re-canary forever? There is no list to walk. There are no next-pointers, no array, no stored path. There is a current stage identifier and a rule that computes the next one. That rule is the **step function** `f`, and the walk `s, f(s), f(f(s)), ...` is an implicit sequence. Two pointers moving at different speeds over it can answer the looping question in constant space — but only if `f` satisfies a short list of properties, and knowing that list is the difference between recognising the pattern and misapplying it. ## Requirement 1: deterministic and free of side effects The faster pointer advances two steps for every one the slower takes, which means the step function is invoked on the same value that the slow pointer will later reach. If `f` consults a random source, a clock, a mutable counter, or the path taken so far, the two pointers are no longer walking *the same* sequence, and equality between them means nothing. Side effects are the same problem wearing a different hat. If evaluating `f` advances a cursor, consumes from a stream, writes an audit row, or mutates the state it was asked about, then evaluating it three times per element — which this pattern does — corrupts the very thing being examined. The step must be a pure query, safe to call again on the same input and get the same answer. ## Requirement 2: exactly one successor — a functional graph This is the requirement candidates skip, and it is the one that decides whether the pattern applies at all. Draw every reachable state as a node with an arrow to its successor. If each node has exactly **one** outgoing arrow, the structure is a *functional graph*: from any start there is a single path, and that path is precisely a chain. Two pointers can race along it because there is only one road. If some state can transition to either of two successors depending on an external signal, there is no single path — there is a branching structure with many walks, and "the fast pointer met the slow pointer" tells you nothing about whether any particular walk loops. That situation needs a traversal-based technique instead, and it is a genuinely different problem. So before reaching for two pointers, verify the modelling claim: *one state in, exactly one state out*. ## Requirement 3: the walk never simply ends Consider this fragment, which is the pattern in its bare form: ``` slow = start fast = start while true: slow = f(slow) fast = f(f(fast)) if slow == fast: return "cycle" ``` It has no exit other than finding a cycle. That is correct only when `f` is **total** on the reachable set — every state genuinely has a successor. In the deployment engine, a terminal stage such as "promoted" has no next transition. If the walk can end, the fragment is wrong as written: the loop must test whether `fast` or its successor is terminal *before* advancing and return "no cycle" there. Notice the asymmetry — the fast pointer reaches a terminal state first, so it alone needs the guard, and it needs it after both of its two hops. ## Requirement 4: a finite reachable set, and a cheap step Finiteness is what upgrades "a cycle might exist" to "a cycle is guaranteed unless the walk terminates", and it also bounds the running time: with a tail of `μ` steps and a cycle of `λ`, the pointers meet within about `μ + λ` iterations, so the work is linear in the number of distinct states reached, with constant extra memory. Cost matters because the pattern trades evaluations for memory. Each iteration performs three applications of `f` — one for the slow pointer, two for the fast — so roughly three times the step work of a single straightforward pass, and it caches nothing. When `f` is arithmetic or an array read, that is free. When `f` means a remote lookup or a database round trip, tripling it is a real bill, and recording visited states instead may be the better trade. ## What the technique gives you, and what it does not A meeting of the two pointers proves a cycle exists, in `O(1)` extra space. It does not by itself tell you which states form the loop, how long the loop is, or where the tail joins it — those come from separate follow-up phases. And it never gives you the visited path, so it cannot answer "show me the stages this deployment bounced between" without additional work. Knowing the boundary of what the meeting proves is exactly what stops a candidate from over-claiming in an interview. ## Recognising the pattern in the wild The cue is not the word "list". It is: *deterministic single-successor transition, bounded state space, no end marker, and a memory ceiling*. When those four line up, an implicit chain is hiding in the problem no matter what the domain looks like on the surface.

  • The fragment loops forever with no exit. Where exactly does a termination guard go?
    On the fast pointer only, and after each of its two hops. It reaches a terminal state strictly before the slow pointer can, so checking whether `fast` has a successor before each advance is sufficient; when it does not, the routine returns "no cycle". Guarding the slow pointer instead is dead code, and guarding only once per iteration lets the second hop run off the end.
  • A stage can transition to one of two successors depending on an operator decision. Does the pattern still apply?
    No. Two outgoing transitions means the structure is no longer a single chain, so there is no unique walk for two pointers to race along, and a meeting would prove nothing about any specific path. The modelling claim the pattern rests on — one state in, exactly one state out — has failed, and detecting loops there is a different problem requiring a traversal that records where it has been.
  • Why does the pattern care whether the step function is expensive?
    Because it deliberately spends computation to save memory. Each iteration applies the step three times — once for the slow pointer, twice for the fast — and caches nothing, so an expensive step is paid for repeatedly. With arithmetic or an array read that is irrelevant; with a remote lookup per step it can dominate, and recording visited states so each transition is computed once may be the cheaper trade overall.

Two auditors replay the same deployment from the same stage, one advancing a stage at a time and the other two at a time. That only works if replaying a stage always yields the same next stage and never actually re-triggers it.

saying these in an interview costs you the question

  • Applies the pattern to a state that can branch to two successors
  • Ignores that the walk may hit a terminal state
  • Assumes the step function may safely mutate state it reads
  • Claims the meeting also reveals the loop's contents
  • Forgets the step is evaluated about three times per element

context

open as a page

When is a visited-set loop check the right call over constant-space fast/slow pointers?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Use a visited set when the state count is small, when the step is expensive enough to want each transition computed once, or when the report must name the looping states. Use two pointers when memory is the binding constraint.

open as a page

Why does iterating x = f(x) over a finite value range always repeat a value?

level: juniorimportance: should knowfreq 52%

basics

~20 s

With finitely many values, among the first N+1 terms two must be equal — pigeonhole. Because the step depends only on the current value, that repeat locks the sequence into a loop forever: tail, then cycle.

open as a page

Why does following i to a[i] in an array whose values are all valid indices guarantee a loop?

level: middleimportance: should knowfreq 45%

basics

~20 s

Every slot holds exactly one valid index, so each position has exactly one successor over finitely many positions. That deterministic walk can never end and can never avoid revisiting a position, so it must fall into a loop.

open as a page