skip to content

In counting sort's output pass, why is the input scanned right-to-left rather than left-to-right?

level: middleimportance: should knowfreq 45%

answer

  1. Stability is about equal keys only
  2. What do the counters point at?
  3. Blocks are filled from the end
  4. Feed a backward-filling block backwards
  5. Direction must match the index convention

basics

~20 s

The counters hold exclusive end indices, so each key's block fills backwards. Feeding the input from the right therefore places the last of several equal keys last, preserving their original order; a forward scan with the same counters reverses equal records.

solid answer

~50 s

Stability is not a free property of counting sort — it comes from pairing the index convention with the traversal direction. In the standard form `count[v]` holds the number of keys `<= v`, which is one past the last slot of `v`'s block, so each write pre-decrements and fills that block from its end toward its start. To have the earliest equal record end up earliest, you must place records from the end of the input backwards. Run the same loop forward and the output is still correctly sorted by key, but records sharing a key come out in reverse input order — which silently destroys any ordering an earlier stage established. The forward direction can be made stable too, by storing exclusive *start* offsets (the count of keys strictly less than `v`) and post-incrementing. Direction and convention must match.

code

pseudocode · 7 lines
pseudocode
// count[v] = how many keys are <= v (prefix sums done)
for i in length(a)-1 down to 0:
    v = key(a[i])
    count[v] = count[v] - 1
    out[count[v]] = a[i]
// each block filled from its last slot toward its first
...

go deeper

for a junior

Know what stability means — equal keys keep their original relative order — and that it matters only when records carry data beyond the key. Recognise that a sort can be correct and still be unstable.

for a middle

Explain the mechanism, not the label: the counters hold block ends, blocks fill backwards, so the input is fed backwards. Be able to say what changes if the loop runs the other way.

for a senior

Catch this in review and prescribe the test that would have caught it — records with a secondary sequence value, asserted ascending within each key group. Asserting only non-decreasing keys is the gap.

for a principal

Decide where stability belongs as a documented contract of a shared sorting utility rather than an accident of one implementation, so downstream stages can depend on it without reading the loop.

## What stability means here A sort is **stable** if records with equal keys appear in the output in the same relative order they had in the input. It is a promise about **equal** keys only — it says nothing about the ordering of distinct keys, which every correct sort gets right anyway. The setting that makes it matter: an image pipeline produces one record per detected region, already emitted in scan order (top-left to bottom-right), and a later stage groups them by an 8-bit average-luminance band. Within a band, downstream code assumes the regions are still in scan order. A stable sort keeps that promise for free; an unstable one quietly breaks it, and the failure surfaces far away as "the overlay draws regions in a jumbled order", not as a sorting bug. ## Why the direction is load-bearing After the prefix-sum pass, `count[v]` is the number of keys `<= v`. Read as an address, that is the **exclusive end** of value `v`'s block: the block occupies `[count[v-1], count[v])`. The placement loop therefore pre-decrements before writing, so the first record of key `v` that it places lands in the *last* slot of the block, the next one in the second-to-last, and so on: **the block fills backwards**. If a block fills backwards, the records must be fed to it backwards for their original order to survive. Hence the loop: ``` for i in length(a)-1 down to 0: v = key(a[i]) count[v] = count[v] - 1 out[count[v]] = a[i] ``` The last input record with key `v` is seen first and takes the block's last slot; the first input record with key `v` is seen last and takes the block's first slot. Relative order preserved. ## What a forward loop actually does Change only the direction, keeping the same counters and the same pre-decrement, and the result is still **sorted**: every record still lands inside the block belonging to its key, so nothing is misordered by key. But within each block the fill order is reversed relative to the input, so equal keys come out backwards. This is the failure a reviewer must be able to spot, because tests that only assert "the output is non-decreasing by key" pass happily. The bug is invisible to the obvious assertion and visible only to a test that checks a secondary ordering — which is the test worth writing. ## The other stable form Stability is not a property of "backwards" as such. Store the complementary convention — `start[v]` = number of keys strictly **less than** `v`, i.e. the block's inclusive start — and then a forward loop with post-increment is stable: place `a[i]` at `start[v]`, then `start[v] += 1`. First equal record gets the first slot. Both forms are stable; the mismatched pairings (exclusive end + forward, or exclusive start + backward) are the unstable ones. So the honest answer to "is counting sort stable?" is: *this* implementation is, and here is the invariant that makes it so. Reciting "counting sort is stable" without being able to point at the line that makes it true is the weak answer interviewers are probing for. There is a third variant worth separating out: the in-place version that skips placement entirely and re-emits each key value `count[v]` times over the input. People call it stable, but the label is vacuous — it only works when records are bare keys, and equal bare keys are indistinguishable, so no observable order can be preserved or destroyed. Stability is meaningful exactly when equal keys carry distinguishable payloads. ## Cost of the guarantee None, in asymptotic terms. Both stable forms are the same three passes, O(n + k) time and O(n + k) space. Stability here is bought purely by getting an index convention and a loop direction to agree — which is why it is a review question rather than a design tradeoff. What it does cost is the separate output buffer: you cannot permute records into place within the input array and keep the guarantee without extra bookkeeping, so the n-sized output array is part of the price. ## How to review it Three checks, in order. Does the counters array hold ends or starts after accumulation? Does the placement loop pre-decrement or post-increment? Does it run forward or backward? Ends pair with pre-decrement and a backward scan; starts pair with post-increment and a forward scan. Any other combination is either unstable or out of bounds — and a test that sorts records carrying a secondary sequence number, then asserts that sequence numbers within each key group are ascending, catches all of them.

  • If the loop runs forward with the same counters, is the output still sorted?
    Yes — every record still lands inside the block that belongs to its key, so the output is non-decreasing by key. What changes is the order *within* a block: equal keys come out in reverse input order. That is why a test asserting only "non-decreasing by key" cannot catch this; you need records carrying a secondary sequence value and an assertion that it stays ascending inside each key group.
  • Can a forward placement loop be stable?
    Yes, with the complementary convention. Store the number of keys strictly less than `v` — the block's inclusive start — then place each record at `start[v]` and post-increment. The first equal record takes the first slot and order is preserved. Stability comes from ends-with-pre-decrement-backwards or starts-with-post-increment-forwards; mixing the halves is what breaks it.
  • When does stability make no observable difference at all?
    When equal keys are indistinguishable. Sorting bare small integers, two records with key 7 are the same record for every purpose, so no rearrangement among them is detectable and the guarantee is vacuous. It becomes meaningful the moment a key has a payload, or an earlier stage established an ordering that downstream code relies on within each key group.

saying these in an interview costs you the question

  • Recites that counting sort is stable without naming the mechanism
  • Thinks a forward placement loop produces unsorted output
  • Believes the traversal direction is arbitrary style
  • Claims stability costs an extra pass or extra memory
  • Calls the in-place count-and-emit variant meaningfully stable
  • Tests only that the output is non-decreasing by key

context