Cyclic sort can swap repeatedly without advancing its index — why is the total work still O(n)?
answer
- Count over the whole run, not per iteration
- What does one swap accomplish permanently
- Can a settled slot ever be disturbed
- Bounded quantity that only increases
- Fewer than n swaps in total, plus n advances
basics
~20 sEvery swap parks one value permanently on its home index, and a settled index is never disturbed again. So total swaps are capped by the slot count, and the cursor advances at most that many times — linear overall.
solid answer
~50 sThe nested-looking shape is misleading; you have to count globally instead of per iteration. Each swap sends the value held at the current slot to its home index, and once a slot holds its own value the guard never swaps it away again — any later value whose home is that slot would have to equal what already sits there, and equality is precisely the case where the loop skips the swap. So each swap converts one unsettled slot into a permanently settled one, capping swaps at fewer than `n` for the entire run. The index pointer also advances at most `n` times. Total work is therefore O(n) with O(1) extra space, for every input in the licensed range — this is an aggregate bound over the whole run, not an average over random inputs, and no adversarial arrival order can push it toward quadratic.
code
pseudocode · 8 linesi = 0
while i < length(a):
home = a[i] - 1 // value v belongs at index v-1
if a[i] != a[home]:
swap(a[i], a[home]) // i does NOT advance here
else:
i = i + 1
// on exit: every value present sits on its own indexgo deeper
Know that the pattern is linear overall even though it sometimes swaps several times before moving forward. Remember the reason in one line: each swap parks a value on its home for good.
Deliver the counting argument cleanly — swaps only ever increase the settled-slot count, settled slots are never vacated, so total swaps are capped by the slot count. This is the mechanics tier the question is really testing.
Distinguish an aggregate worst-case bound from an average over inputs, and note that the scatter access pattern makes the constant worse than a sequential pass even where the asymptotics win.
Own the framing that a linear bound is a growth promise, not a speed promise. Be able to argue when to accept a worse-asymptotic but cache-friendly approach on a latency-sensitive path.
## The shape that misleads people Cyclic sort's placement loop repeatedly swaps *without* moving its cursor forward. Read locally, that looks like unbounded work per position, and the reflex answer — "a repeat-until-settled inside a walk over `n` slots is O(n^2)" — is the single most common wrong answer this pattern draws in interviews. The fix is to stop counting per position and count over the whole run instead. ## The counting argument Call a slot **settled** when it holds the value whose home it is. The claim is that every swap increases the number of settled slots by exactly one, and that a settled slot never becomes unsettled. *Every swap settles a slot.* A swap happens only when the value `v` at the cursor does not match what currently sits at `v`'s home. The swap deposits `v` into that home. By definition, that slot is now settled. *No swap unsettles a slot.* Suppose some slot `h` is already settled — it holds the value whose home is `h`. For a later swap to target `h`, the cursor would have to be holding a value whose home is also `h`, meaning the cursor's value equals the value already at `h`. But that equality is exactly the condition under which the loop *skips* the swap and advances instead. So a settled slot is never touched again. Put together: the settled count starts at zero or more, never decreases, increases by one per swap, and is bounded above by the number of slots. Therefore fewer than `n` swaps occur across the entire run. The cursor separately advances at most `n` times. Placement is O(n); the read-off pass afterwards is another O(n). Extra space is O(1) — the swaps happen inside the buffer. This is a potential-function argument in miniature: pick a quantity that only moves in one direction and is bounded, then charge each expensive step to a unit of it. ## Aggregate, not average Be precise about what kind of bound this is. It is not an average over random inputs, and it is not amortized in the sense of a data structure that occasionally pays a big cost and spreads it over cheap operations. It is a **worst-case aggregate bound**: for *any* arrangement of the licensed values, the total number of swaps is capped by counting settled slots. An adversary who gets to choose the arrival order gains nothing, because the counting argument never mentions the order. That distinction matters in a follow-up. If you say "O(n) on average", the natural next question is what the bad case looks like — and you will not be able to produce one, because there isn't one. ## Duplicates do not break the bound A reasonable worry: with duplicates present, some value has no home left, so surely the loop grinds. It does not. The guard skips whenever the destination already holds its own value, which is precisely what a second copy of an already-settled value encounters. The cursor then advances and the duplicate is simply left where it stands, to be reported by the read-off pass. Every swap that *does* happen still settles a slot, so the cap holds unchanged. ## The cost lens that actually matters Asymptotically this is linear, and it beats the Omega(n log n) floor that binds comparison sorts — legitimately, because it never compares two values to each other, only a value against the slot it is standing on. But the constant is not zero: the access pattern is a scatter. Sending values to arbitrary home indices jumps around the buffer, so the memory behaviour is worse per operation than a straight sequential pass. On small inputs the asymptotic win is invisible and a simpler approach may finish first; the linear bound is a promise about growth, not a promise of being fastest at every size. ## How to say it in an interview Three sentences is enough: each swap puts one value permanently home; a home is never vacated once filled, because the equal-value case is exactly the skip case; so total swaps are capped by the slot count and the whole thing is linear. Then add the qualifier that turns a good answer into a strong one — this holds for every input, not on average.
- Is this an amortized bound or a worst-case bound?Worst-case aggregate. The counting argument never assumes anything about the arrival order, so the cap on swaps holds for every licensed input, including one chosen adversarially. Calling it average-case invites a follow-up about the bad input, and there is no bad input to describe.
- Do duplicate values increase the number of swaps?No. A duplicate meets a destination that already holds its own value, which is the skip branch, so the cursor advances and no swap happens. Swaps that do occur still settle one slot each, so the cap is unchanged and the duplicate is simply reported by the later scan.
- If it is O(n) and comparison sorts are O(n log n), why is that not a contradiction of the sorting lower bound?The Omega(n log n) floor applies to sorts that learn about the input only by comparing elements to each other. This pattern never does that; it reads a value and computes an address from it, which is extra information the comparison model does not have. The bound simply does not apply.
saying these in an interview costs you the question
- Says a repeat-until-settled loop inside a walk is O(n^2)
- Calls the linear bound average-case rather than worst-case
- Claims duplicates push the cost toward quadratic
- Thinks it violates the comparison-sort lower bound
- Cannot say what one swap accomplishes permanently