Counting Sort
Count occurrences of each key in a small integer range, then place elements by prefix sums — linear time, no comparisons.
Overview
Counting sort sorts integers (or anything with a small integer key) in O(n + k) where k is the size of the key range. It never compares elements: it tallies how many times each key appears, converts the tallies to starting positions with a Prefix Sum, and drops each element into its slot. This beats the Ω(n log n) lower bound because that bound applies only to comparison sorts.
The stable version (placing elements by iterating the input in order) is the building block of Radix Sort. It needs O(n + k) extra space, so it is only sensible when k is not much larger than n — sorting exam scores 0–100, ages, bytes, characters, or small enumerations.
Intuition
A mental model before the formal terms.
Sorting 10,000 exam papers by score 0–100: rather than comparing papers, set up 101 labelled piles and drop each paper on its pile in one pass. Then read the piles in order. The number of papers is irrelevant to how many piles you need.
How it works
- Find the key range
[min, max]; letk = max - min + 1. - Build
count[0..k): for each element,count[key - min] += 1. - Turn counts into positions:
count[i]becomes the number of elements with key< i(exclusive prefix sum) — the starting index of keyiin the output. - Iterate the input left to right, placing each element at
out[count[key]]and incrementingcount[key]. Left-to-right placement makes the sort stable. - For plain integers without satellite data, simply emit each key
count[i]times.
Why it works
After the prefix sum, count[i] is exactly the number of elements with key less than i, which is the 0-based index where the first element with key i belongs in sorted order.
Placing elements in input order with an incrementing cursor per key preserves the original relative order of equal keys — stability.
Every step is a single pass over n elements or k buckets: O(n + k) time and space.
Recognition
How to tell a problem wants this.
- Keys are integers in a small known range ("values between 0 and 1000", "ages", "characters a–z", "0/1/2 colors").
- Constraints say
n ≤ 10^6withmax value ≤ 10^6— a comparison sort works but linear time is the intended answer. - You need a frequency histogram anyway (anagram checks, top-k by count).
- A stable sort by a small key is required as a subroutine (Radix Sort, bucket by digit).
Interactive visualization
Play, step, change the input. ← → and space work too.
1k = max(a); count = [0] * (k+1)2for x in a: count[x] += 13for v in 1 .. k: count[v] += count[v-1] # prefix sums4for x in reversed(a):5 count[x] -= 16 out[count[x]] = x7copy out into aPseudocode
1mn, mx = min(a), max(a); k = mx - mn + 12count = [0] * k3for x in a: count[x - mn] += 14pos = exclusive prefix sum of count5for x in a (in order): out[pos[x - mn]] = x; pos[x - mn] += 16return outImplementations
1def counting_sort(a: list[int]) -> list[int]:2 if not a:3 return []41 · Find the key range5 mn, mx = min(a), max(a)6 k = mx - mn + 172 · Count occurrences of each key8 count = [0] * k9 for x in a:10 count[x - mn] += 1113 · Exclusive prefix sums: count[i] = number of elements < i + mn12 total = 013 for i in range(k):14 count[i], total = total, total + count[i]154 · Place each element at its slot, left to right (stable)16 out = [0] * len(a)17 for x in a:18 out[count[x - mn]] = x19 count[x - mn] += 120 return outmin(a), max(a)are two C-speed passes; fine for clarity.[0] * kallocates the histogram; Python ints never overflow so counts are safe.- The tuple assignment
count[i], total = total, total + count[i]evaluates the right side first, producing exclusive prefix sums in one line. - Placement in input order with a post-increment (
count[...] += 1after the write) is stable. - A new list is returned; use
a[:] = counting_sort(a)to sort in place.
collections.Counter(a) builds the histogram in C, but iterating keys in sorted order costs O(k log k) unless you loop over range(mn, mx + 1).
Counter+sorted(counter.items())is the idiomatic key-only version when k is small.- For sorting records by key,
sorted(a, key=...)is already stable and O(n log n); counting sort only wins when k is small relative to n. - Lists of ints are boxed objects in CPython;
array.array('i')or NumPy (np.bincount) are far faster for big histograms.
- Building
countwith a dict and forgetting to iterate keys in sorted order. - Not offsetting by
mnfor negative keys (negative indices wrap silently in Python and corrupt the histogram). - Using
count[x - mn] += 1before the write in the placement loop (off by one).
- Histogram storage: C++
std::vector<int>is contiguous and zeroed; JS/TS need.fill(0)(orInt32Array, which is zeroed and faster); Python[0] * kboxes each int. - Negative indices: Python wraps
count[-1]to the last slot silently, C++ is undefined behaviour, JS creates a string property — always offset bymn. - Range overflow:
mx - mn + 1can overflowintin C++; JS doubles lose precision past 2^53; Python is unbounded. - Stability is identical in all four versions (left-to-right placement with exclusive prefix sums), which is what lets each serve as a radix-sort digit pass.
Complexity
k = size of the key range. Stable (with left-to-right placement), not in-place, not comparison-based.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Integer keys with range
k = O(n)— scores, ages, bytes, characters, enum codes. - As the stable digit-sort inside Radix Sort.
- When you already need a histogram of the values.
- Sorting a small range of colors/categories in a single pass (Dutch national flag alternative, with extra space).
- Keys spread over a huge range (
k ≫ n, e.g. 64-bit integers or floats) — the count array is too big; use Radix Sort or a comparison sort. - Non-integer keys with no cheap integer mapping (strings of arbitrary length, objects under a custom comparator).
- In-place sorting is required.
Alternatives
Common mistakes
- Assuming keys start at 0 and indexing
count[x]with negative or offset values — normalize bymin. - Placing elements by iterating the input right to left with inclusive prefix sums but then also iterating left to right — mixing the two conventions breaks stability or overwrites slots.
- Forgetting that stability only matters (and only holds) when elements carry satellite data.
- Allocating
countof sizemaxinstead ofmax - min + 1for large minimums.
Interview patterns
- Sort colors (0/1/2): count then overwrite; or Dutch national flag in place.
- Group anagrams / valid anagram via 26-letter count arrays.
- Top-k frequent elements with bucket-by-frequency (counting sort on counts).
- H-index: count papers per citation count capped at
n.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Hash map or array?Beginner
- Where does O(n log n) come from?Beginner
- Minimum Size Subarray SumIntermediate
- Two SumBeginner
- Longest Substring Without Repeating CharactersIntermediate