Visual walkthrough — Counting sort — O(n + k), integer keys only
Step 1 — The problem, drawn as blocks
WHAT. We have a row of items. Each item carries a key — a small whole number. Our job: rearrange them so keys go from smallest to largest.
WHY start here. Before any algorithm, we need to see what we own. Every item is a block; the number written on it is its key. Sorting = sliding blocks along a shelf until the numbers rise left-to-right.
PICTURE. The top row is the input . Below it, the goal row — same blocks, reordered. Notice we haven't decided how to get there yet. We only know:
- = how many blocks (here ).
- = the largest key that can appear (here , so keys live in ).
The whole trick that follows: we will never ask "is block A bigger than block B?" We only ask "how many blocks have value ?" — using itself as a shelf address.
Step 2 — Count how many of each value (the tally)
WHAT. Make a fresh array count with one cell per possible value, i.e. indices through . Set all to zero. Walk through once; for each block of value , add to count[v].
WHY. To place value later, we first need to know how many 's exist. You can't decide where the 's go until you know there are three of them. This scan costs : one touch per block.
PICTURE. Watch the arrows: each block drops a tally mark into its own column. The block labelled always aims at column — the key is the address. That's the non-comparison heart of the method.
count[v]— the counter belonging to value .- — bump it by one every time we meet that value.
After scanning all of :
Step 3 — Turn counts into positions with a prefix sum
WHAT. Replace each count[v] by the running total of everything up to and including :
WHY THIS TOOL — why a prefix sum and not something else? We want each value's final position on the shelf. A value 's block starts right after all smaller values have been laid down. "How many blocks are ?" is precisely a cumulative sum of the tallies. No other operation converts "how many of each" into "where does each end" in one linear pass. This is the exact tool for the exact question.
PICTURE. Each bar grows by absorbing the bar to its left. The dotted stack on the right shows the shelf filling up: after the sweep, the height of column is the first free index past 's block.
Applied left to right, becomes:
Now read it as a promise: count[v] = number of keys .
Step 4 — Read a value's home block off the array
WHAT. Fix a value . Define two boundaries:
Then value 's blocks occupy exactly output indices up to .
WHY. This is why the prefix sum was worth doing. Everything smaller than takes the first slots. Everything equal to comes next, and there are of them, filling . Nothing overlaps, nothing is skipped — the blocks tile the shelf perfectly.
PICTURE. The shelf is coloured into contiguous blocks. For value : and , so the three 's own slots .
Recall Check the block widths against the tally
Value : width 2. Value : width 2. Value : width 3. Value : width 1. Total . ✓
Step 5 — Place the blocks (scan input right-to-left)
WHAT. For each block in , taken from the last toward the first, with key :
Decrement first, then write.
WHY the direction matters. count[v] currently holds — one past the block. Decrementing gives , the highest free slot for . We fill 's block from its back. Because we also read from its back, the last in the input claims the last slot — so equal keys keep their original order. That property is called stability.
PICTURE. Follow the trace: the rightmost input block is a ; it walks to the top of column 's block. The next-from-right is another value, and so on. Each write pulls the column's ceiling down by one.
Full trace on , reading right→left:
| block read | count[v] before → after |
write index | |
|---|---|---|---|
3 (last) |
3 | 7 → 6 | out[6]=3 |
0 |
0 | 2 → 1 | out[1]=0 |
3 |
3 | 6 → 5 | out[5]=3 |
2 |
2 | 4 → 3 | out[3]=2 |
0 |
0 | 1 → 0 | out[0]=0 |
3 |
3 | 5 → 4 | out[4]=3 |
5 |
5 | 8 → 7 | out[7]=5 |
2 (first) |
2 | 3 → 2 | out[2]=2 |
Result: . Sorted — and not a single comparison was made.
Step 6 — Edge case: negative or shifted keys
WHAT. Array indices start at ; you cannot write count[-2]. If keys are , shift each key by so the smallest maps to index :
Sort on shifted indices, then add back to recover the real values.
WHY. Counting sort's superpower is value = address. Addresses must be non-negative and start at . Subtracting the minimum slides any contiguous integer range down onto without changing the order.
PICTURE. The number line slides right by : , , . Same gaps, same order, now index-friendly.
The one-picture summary
Count → prefix-sum → place-from-the-back. Three linear sweeps, zero comparisons. The figure below stacks all three phases so you can see the tally become boundaries become homes.
Recall Feynman retelling — explain it to a friend
"Imagine sorting exam papers by a score from 0 to 5. First, I make six pigeonholes and just tally how many papers got each score — no reading, no comparing, one glance per paper. Second, I add the tallies up as I go, so each pigeonhole now tells me how many papers scored at most this much — which is the same as where this score's block ends on the final stack. Third, I deal the papers back onto the stack, but I go through my pile from the bottom up and drop each paper at the top of its score's block, then lower that ceiling by one. Going bottom-up means two papers with the same score never swap places — the sort stays honest (stable). That's it: count, add up, deal from the back. It's blazing fast because the score is the address, but it only works when the scores are small whole numbers I can use as pigeonhole labels."
Flashcards
Why does counting sort avoid the barrier?
After the prefix sum, what does count[v] mean?
What are and ?
count[v-1]; #keys count[v]. Value occupies output indices .During placement, why decrement count[v] before writing?
count[v] holds , one past the block; decrementing gives the highest free slot .