Counting Sort
Every sort so far compared values with each other. Counting sort does something completely different: it counts. If you know every value is a small number - say between 0 and 100 - you can just tally how many times each value appears and rewrite the array from the tallies. That makes it O(n + k), beating the O(n log n) wall - because comparison sorts cannot go faster, but counting is not comparing.
Watch it sort
Press play - or drag the timeline and step through it yourself.18 values between 0 and 9. Phase 1: count how many times each value appears - no comparing needed.
Complexity
Counting votes
Imagine sorting a box of 10,000 ballots where each ballot is a number from 1 to 5. You would never compare ballots with each other. You would make five piles, throw each ballot on its pile, and at the end say: "1,204 ones, 2,551 twos..." Then you could rebuild the whole sorted sequence just from those five numbers. That is counting sort - the piles are an array of counters.
How it works, step by step
Find the biggest value k, and create a counts array with k+1 slots, all zero.
Walk the input once. For each value v, add one to counts[v]. No comparisons - just a lookup.
Now counts[v] says exactly how many times v appears.
Walk the counts array from 0 up to k. Write each value v into the output counts[v] times.
The output is sorted, because you walked the values in order.
Total work: n steps of counting plus k steps of writing - O(n + k).
The code, in JavaScript
The whole algorithm for non-negative integers: tally, then rebuild. Two loops, no comparisons.
function countingSort(arr) {
if (arr.length === 0) return [];
const max = Math.max(...arr);
const counts = new Array(max + 1).fill(0);
// Phase 1: tally every value.
for (const value of arr) {
counts[value]++;
}
// Phase 2: rebuild the array from the tallies, in order.
const out = [];
for (let v = 0; v <= max; v++) {
for (let c = 0; c < counts[v]; c++) {
out.push(v);
}
}
return out;
}
countingSort([3, 1, 2, 1]); // → [1, 1, 2, 3]Dry run: sorting [3, 1, 2, 1]
The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.
| Step | Phase | Array | What happened |
|---|---|---|---|
| 1 | 1 | [3, 1, 2, 1] | Read 3 - seen 1 time(s) so far. |
| 2 | 1 | [3, 1, 2, 1] | Read 1 - seen 1 time(s) so far. |
| 3 | 1 | [3, 1, 2, 1] | Read 2 - seen 1 time(s) so far. |
| 4 | 1 | [3, 1, 2, 1] | Read 1 - seen 2 time(s) so far. |
| 5 | 2 | [1, 1, 2, 1] | count[1] says 1 appears 2 time(s) - write 1 into slot 1. |
| 6 | 2 | [1, 1, 2, 1] | count[1] says 1 appears 2 time(s) - write 1 into slot 2. |
| 7 | 2 | [1, 1, 2, 1] | count[2] says 2 appears 1 time(s) - write 2 into slot 3. |
| 8 | 2 | [1, 1, 2, 3] | count[3] says 3 appears 1 time(s) - write 3 into slot 4. |
Good choice when…
- Values are integers in a small known range - grades 0-100, ages 0-120, bytes 0-255. Then O(n + k) crushes every comparison sort.
- You are sorting millions of items by a small key - counting sort is a single pass plus a tiny table.
- You need the stable building block inside radix sort - this is exactly what each digit pass runs.
- You want a histogram anyway: the counts array *is* the histogram; the sorted output is almost a side effect.
Bad choice when…
- The range k is huge compared to n. Sorting 10 values that can be anywhere up to a billion would allocate a billion counters - the k in O(n + k) is not free.
- Values are floats, strings, or arbitrary objects with no small integer key. Counting needs values it can use as array indexes.
- Values can be negative and you forget to shift them - a[v] with negative v silently breaks (see gotchas).
Common mistakes
- Counting sort does not beat the O(n log n) "law" by magic - that lower bound only applies to sorts that compare. Counting sort uses the values as addresses instead. Different game, different rules.
- Negative values break the naive version: counts[-3] is not an array slot. Shift by the minimum first (index = value - min) and the range becomes min..max.
- The simple rebuild version is fine for plain numbers, but it loses the original objects - you cannot sort records with it. That is what the stable prefix-sum version is for.
- Watch the memory: `new Array(max + 1)` with max = 2³¹ will not end well. Always know your k before choosing counting sort.
Counting Sort vs. its closest relatives
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Counting Sortthis page | O(n + k) | O(n + k) | O(n + k) | O(k) | yes |
| Radix Sort | O(nk) | O(nk) | O(nk) | O(n + k) | yes |
| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | no |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | yes |
| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | yes |