Counting Sort

Sorting
Stable
avg O(n + k)

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.
Phase 1Counted 0Writes 0Step 1 / 39

18 values between 0 and 9. Phase 1: count how many times each value appears - no comparing needed.

counting writing back final not counted yet

Complexity

Best
O(n + k)
Input already in order
Average
O(n + k)
Normal mixed input
Worst
O(n + k)
Worst possible input
Space
O(k)
Extra memory used
Stable: yes - equal values keep their orderIn-place: no - no copy of the array needed

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

  1. Find the biggest value k, and create a counts array with k+1 slots, all zero.

  2. Walk the input once. For each value v, add one to counts[v]. No comparisons - just a lookup.

  3. Now counts[v] says exactly how many times v appears.

  4. Walk the counts array from 0 up to k. Write each value v into the output counts[v] times.

  5. The output is sorted, because you walked the values in order.

  6. 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.

javascript
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.

StepPhaseArrayWhat happened
11[3, 1, 2, 1]Read 3 - seen 1 time(s) so far.
21[3, 1, 2, 1]Read 1 - seen 1 time(s) so far.
31[3, 1, 2, 1]Read 2 - seen 1 time(s) so far.
41[3, 1, 2, 1]Read 1 - seen 2 time(s) so far.
52[1, 1, 2, 1]count[1] says 1 appears 2 time(s) - write 1 into slot 1.
62[1, 1, 2, 1]count[1] says 1 appears 2 time(s) - write 1 into slot 2.
72[1, 1, 2, 1]count[2] says 2 appears 1 time(s) - write 2 into slot 3.
82[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

AlgorithmBestAverageWorstSpaceStable
Counting Sortthis pageO(n + k)O(n + k)O(n + k)O(k)yes
Radix SortO(nk)O(nk)O(nk)O(n + k)yes
Quick SortO(n log n)O(n log n)O(n²)O(log n)no
Merge SortO(n log n)O(n log n)O(n log n)O(n)yes
Bubble SortO(n)O(n²)O(n²)O(1)yes