Why does following i to a[i] in an array whose values are all valid indices guarantee a loop?
answer
- Read the stored number as a successor
- How many edges leave each position?
- The set of positions is finite
- A position two slots point at
- Distinct values everywhere would mean a permutation
basics
~20 sEvery 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.
solid answer
~50 sThe recognition step is that a value constrained to be a valid index is really a *pointer*. Reading `a[i]` as "the successor of position `i`" turns the array into a graph where every node has exactly one outgoing edge and there are finitely many nodes. That is a total deterministic step over a bounded domain, so the walk can never terminate and can never avoid repeating — it must fall into a loop. The structural payoff: a position with two incoming edges is where a tail merges into the loop, and two incoming edges means two different slots store the same number. So a repeated value in the data shows up as a merge point in the walk, which is why constant-space pointer techniques apply to a read-only array you are forbidden to sort or annotate.
go deeper
Recall the reframing: if the stored number is always a valid position, it acts as a link to the next position. One link out of every slot plus finitely many slots means the walk must come back around.
Explain out-degree one, a finite node set, and why that combination forces a loop; then connect a position with two incoming edges to two slots holding the same number.
Read the constraints as the signal — read-only input plus a constant-space budget over in-range values is the interviewer pointing at an implicit chain — and state what an out-of-range entry or a concurrent writer would break.
Own the general lesson: any value constrained to name another element of the same finite collection is an implicit pointer, which turns a class of problems into cycle problems solvable with no allocation and no mutation of shared data.
## The scenario An audit runs over a shipment manifest: a fixed table where every slot holds a slot number, drawn from a range that is always valid for the table. The manifest is a shared read-only snapshot — other jobs are reading it concurrently, so you may not sort it, may not reorder it, and may not scribble markers into it. You also have a tight memory allowance because the audit runs alongside everything else. On its face this is a table of numbers. The recognition step is seeing that it is a chain. ## Values that are valid indices are pointers Stop reading `a[i]` as "the number stored at position `i`" and start reading it as "**the successor of position `i`**". The step function is `f(i) = a[i]`. Nothing else changes; the same bytes now describe a graph whose nodes are the positions. Two properties of that graph decide everything: - **Out-degree is exactly one.** Every slot holds precisely one number, and that number is a valid position. So every node has exactly one outgoing edge — no branching, no dead ends. This is a *functional graph*, which is the structure the fast/slow pattern requires. - **The node set is finite.** There are only as many positions as the table has slots. Total step plus finite domain means the walk from any start cannot terminate and cannot produce new positions forever. Pigeonhole forces a position to recur, and because the successor depends only on the current position, everything after the recurrence replays with a fixed period. The walk therefore has the standard shape: a tail traversed once, feeding a loop traversed forever. Notice that the guarantee comes from the *range constraint on the values*, not from anything about the numbers themselves. That is the entire recognition leap, and it is why the interviewer's phrasing always insists that every entry is in range. ## Why repeated values become merge points Here is the structural fact that makes this modelling useful rather than merely cute. A node's **in-degree** is how many slots store that node's number. In a functional graph, the place where the tail joins the loop is a node reached from two different predecessors — one on the tail, one on the loop — so it has in-degree at least two. And in-degree at least two means *two distinct slots hold the same number*. So a duplicated value in the data is not merely a fact about the numbers; it is precisely what creates a merge in the walk. Conversely, if every value were distinct and in range, the graph would be a permutation: every node with in-degree exactly one, every walk a pure loop with no tail at all. The presence of a tail is the signature of a duplicate. One subtlety completes the argument. For the loop's entry point to be a merge, the walk must *start outside the loop*. If you begin at a node that no slot points to — a node with in-degree zero — you are guaranteed to be on a tail, so the first loop node you reach genuinely has two predecessors. Beginning at an arbitrary position gives no such guarantee; you might start inside the loop, walk a pure cycle, and learn nothing about merges. Choosing a start with no incoming edge is a real precondition, not a formality. Detecting that the walk loops is the first half of the work. Recovering exactly which node the tail merges into is a separate phase with its own argument, and it is a mistake to claim the detection alone hands it to you. ## Why the constraints in the scenario force this view The obvious approaches all collide with the stated constraints: - **Sort and scan for adjacent equals** — mutates the shared snapshot, and costs `n log n` besides. - **Copy, then sort** — buys back correctness by spending linear memory, exactly what the allowance forbids. - **Mark visited slots in place**, for example by flipping a sign or setting a high bit — mutates a snapshot other readers depend on, and corrupts the values it borrows bits from. - **Record visited positions in a membership structure** — correct and simple, but linear memory again. The pointer-following view survives all four constraints because it stores two positions and writes nothing. When an interviewer stacks "read-only" and "constant extra space" onto array data whose values are in index range, that pairing is the tell: they are asking you to see the implicit chain. ## What breaks the model - **An out-of-range value** makes the step partial: the walk can run off the table, out-degree is no longer one everywhere, and the loop guarantee dies. You would need an explicit termination check, and the answer changes. - **An empty or missing slot** is the same failure in different clothing — a node with no outgoing edge. - **A slot holding a set of successors** turns the functional graph into a general one, where two racing pointers prove nothing. - **A table being written concurrently** destroys determinism: the same position can yield different successors on two visits, so the two pointers are no longer walking the same sequence. The read-only snapshot is not a convenience in this scenario; it is a correctness requirement. ## The transferable idea Any time a value is *constrained to name another element of the same finite collection*, you have an implicit functional graph and every cycle technique becomes available — with no list, no links, and no allocation. Recognising that framing on unfamiliar data is worth far more in an interview than memorising any particular walk.
- What changes if one slot may hold a value outside the valid index range?The step becomes partial and the guarantee collapses. A position with no valid successor is a dead end, so the walk can terminate instead of looping, and out-degree is no longer one everywhere. Any pointer race then needs an explicit check before each advance and must be able to report "no loop". The clean argument depends entirely on every value naming a real position.
- Why does it matter which position the walk starts from?Starting at a position that no slot points to guarantees you begin on the tail, so the first loop node you reach is a genuine merge with two predecessors. Start at an arbitrary position and you may already be inside the loop, walking a pure cycle whose entry tells you nothing. The in-degree-zero start is a precondition of the structural conclusion, not a stylistic choice.
- If every value in range were distinct, what would the walk look like?It would be a permutation: every position with exactly one incoming edge as well as one outgoing edge, so the graph decomposes into pure loops with no tails anywhere. Every walk immediately cycles through its own loop and returns to its start. The existence of a tail is therefore the signature of two slots sharing a value — no duplicate, no merge.
- Why can't you just flip a bit in each visited slot to mark it?Because the table is a read-only snapshot other readers depend on, so any mutation is a correctness bug for them, and borrowing a bit corrupts the value it is stolen from. In-place marking is a legitimate technique on data you own, but the read-only constraint is exactly what an interviewer adds to rule it out and push you toward a walk that writes nothing.
A cloakroom where every ticket names another peg in the same cloakroom. Follow the tickets and you can never fall off the end and never wander forever, so you must come back to a peg you have already stood at.
saying these in an interview costs you the question
- Treats stored numbers as data rather than successors
- Thinks a loop only exists if values repeat many times
- Starts the walk anywhere without checking incoming edges
- Assumes in-place marking is available on a shared snapshot
- Claims out-of-range values would not affect the argument