Counting sort
Sort small whole numbers by counting how many times each value appears, then writing the values back in order.
O(n + k)Space O(n + k)5 practice problemsStep-through pictureThe idea
Counting sort never compares two items. Instead, it uses the values themselves as positions. If every item is a whole number from 0 to maxValue, make an array counts with one slot per possible value, and go through the list adding 1 to counts[value] for each item.
Now counts says, for example, "two 1s, one 2, zero 3s, one 4". Walk through the values from smallest to largest and write each value out as many times as it was counted. The result is sorted.
This takes O(n + k) time, where k is the number of possible values. When k is small, like star ratings from 1 to 5 or ages from 0 to 120, that beats any comparison sort. When k is huge, like any 32-bit number, the counts array would be too big.
An everyday example
Tallying votes on a whiteboard
A class votes for its favorite day of the week. Instead of sorting the paper ballots, the teacher draws seven columns on the board, Monday to Sunday, and makes a tally mark under the right day for each ballot. To read the votes out in order, she goes column by column: "Monday, Monday, Tuesday, Thursday...", saying each day once per tally mark.
| In the example | In the code |
|---|---|
| The columns on the board | counts, one slot per value |
| A tally mark for a ballot | counts[value]++ |
| Going through the columns in order | for (let value = 0; value <= maxValue; value++) |
| Reading a day once per mark | Pushing value counts[value] times |
From the slow way to counting sort
- Every sort so far compares two items at a time. There is a proven limit to that: any method that learns the order only through comparisons needs about
n × log₂ ncomparisons in the worst case. To beat it, we must learn about the order some other way. - Suppose the items are small whole numbers, like scores from
0to4. Then the value itself tells us where it goes: all the0s come first, then all the1s, and so on. We don't need to compare3with1; we only need to know how many0s,1s,2s,3s, and4s there are. - Counting is one pass. Make an array
countswith one slot per possible value, all zero, and add 1 tocounts[value]for each item. Array slots are already in order, slot0before slot1, so the counts are sorted by value for free. - To produce the output, walk
valuefrom0tomaxValueand write it outcounts[value]times. The cost is one pass over thenitems plus one pass over thekpossible values. Count each value, then write the values out in slot order: that loop is counting sort.
How it works
- Create
counts, an array ofmaxValue + 1zeros. - For each
valuein the list, add 1 tocounts[value]. - Walk
valuefrom0tomaxValue. - Push
valueintoresultcounts[value]times. - Return
result.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function countingSort(items, maxValue) {const counts = new Array(maxValue + 1).fill(0);for (const value of items) {counts[value]++;}const result = [];for (let value = 0; value <= maxValue; value++) {for (let c = 0; c < counts[value]; c++) {result.push(value);}}return result;}
How fast it is
| Time | O(n + k) |
|---|---|
| Extra memory | O(n + k) |
k is maxValue + 1, the number of possible values. Counting touches each of the n items once, and writing out visits each of the k counters once.
- Best case
O(n + k)Every input costs the same: one pass over the
nitems and one over thek = maxValue + 1slots. With star ratings,kis tiny and it is effectivelyO(n). - Average case
O(n + k)The order of the input doesn't matter at all. Only
nandkdo. - Worst case
O(n + k)When
kis much bigger thann, like 10 items with values up to 1,000,000, the slot walk dominates: a million checks to sort ten numbers.
Common mistakes
- Using it on negative numbers.
counts[-3]is not an array slot. Shift every value by the minimum first (value - min), then shift back. - Using it when the range is huge, such as timestamps in milliseconds. The
countsarray would need billions of slots. - Forgetting the
+ 1inmaxValue + 1, so the largest value has no counter. - Rebuilding only the keys when sorting records. The rest of each record is lost; use the stable placement version.
When to use it
Look for these clues in a problem:
- The values are whole numbers in a small known range, such as ratings, ages, scores out of 100, or priority levels.
- You need
O(n)sorting and the range of values is not much bigger than the number of items. - You need to sort records by a small integer key while keeping equal keys in their original order.
Check yourself
Answer each one in your head first, then open it.
How can counting sort beat the n × log₂ n limit for sorting?
That limit only applies to sorts that learn the order by comparing items. Counting sort never compares; it uses each value as an array index, which a comparison sort can't do.
How many slots does counts need for values from 0 to 99?
100, which is maxValue + 1. Slot 99 must exist for the value 99.
Why is counting sort a bad choice for 10 timestamps in milliseconds?
Today's timestamps are about 1.8 trillion, so counts would need that many slots. The k part of O(n + k) swamps the n = 10 items.
Why does this simple version not need to worry about stability for plain numbers?
Equal numbers can't be told apart, so their order doesn't matter. Stability only matters when items carry other data, like records with the same key.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 5 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
After the counting loop, the rule is: counts[v] equals the number of items equal to v, for every v from 0 to maxValue. It starts true with all zeros and no items seen, and each counts[value]++ keeps it true for one more item.
The writing loop keeps a second rule: result holds every item with a value smaller than value, in sorted order. Writing value exactly counts[value] times adds all items equal to value, and they belong right after everything smaller. So when value passes maxValue, result holds every item, in order.
Both loops end: the first after n items, the second after maxValue + 1 slots, and each inner loop after counts[value] pushes. The total number of pushes is exactly n, so the output has the same length as the input. All of this depends on one promise from the caller: every item is a whole number from 0 to maxValue.
Going deeper
3 more ideas
Why it works without comparing
Array indexes are already in order: slot 0 comes before slot 1, which comes before slot 2. Counting puts each value's tally in its own slot, so walking the slots from left to right visits the values in sorted order. Nothing is ever compared with anything.
The stable version for records
To sort records (like tasks with a priority) rather than bare numbers, turn counts into starting positions: a running total where start[v] is how many items have a key smaller than v. Then walk the records in their original order and place each one at start[key], adding 1 afterwards. Items with the same key land in their original order, so the sort is stable. Radix sort depends on this.
How long it takes
Counting is one pass over the n items, and writing out is one pass over the k slots plus the n items: O(n + k) time and O(k) extra space for counts. For star ratings, k is 6, so it is just O(n). For values up to a billion, k is a billion, which is far too much memory.
Work it by hand
Every step in a table, with why
Sort 7 scores from 0 to 4. Watch counts fill up, then result grow, including a value (2) that never appears.
items = [3, 0, 4, 3, 1, 4, 3], maxValue = 4
| Step | Phase | value | counts | result | What happens | Why |
|---|---|---|---|---|---|---|
| 1 | Count | 3 | [0, 0, 0, 1, 0] | [] | counts[3]++ | counts starts as maxValue + 1 = 5 zeros. The first item adds one 3. |
| 2 | Count | 0 | [1, 0, 0, 1, 0] | [] | counts[0]++ | A 0 uses slot 0. Values are indexes, so 0 needs no special case. |
| 3 | Count | 4 | [1, 0, 0, 1, 1] | [] | counts[4]++ | 4 is maxValue, the last slot. This is why the array has maxValue + 1 slots. |
| 4 | Count | 3 | [1, 0, 0, 2, 1] | [] | counts[3]++ | A repeat just raises the count. |
| 5 | Count | 1 | [1, 1, 0, 2, 1] | [] | counts[1]++ | One 1 so far. |
| 6 | Count | 4 | [1, 1, 0, 2, 2] | [] | counts[4]++ | The second 4. |
| 7 | Count | 3 | [1, 1, 0, 3, 2] | [] | counts[3]++ | The counting pass is done after 7 steps, one per item. No two items were ever compared. |
| 8 | Write | 0 | [1, 1, 0, 3, 2] | [0] | Push 0 once | Walk value from 0. counts[0] is 1, so the inner loop pushes one 0. |
| 9 | Write | 1 | [1, 1, 0, 3, 2] | [0, 1] | Push 1 once | Slot 1 comes right after slot 0, so the output stays in order. |
| 10 | Write | 2 | [1, 1, 0, 3, 2] | [0, 1] | counts[2] is 0: skip | The inner loop runs zero times. Empty slots still cost one check each, which is the k in O(n + k). |
| 11 | Write | 3 | [1, 1, 0, 3, 2] | [0, 1, 3, 3, 3] | Push 3 three times | Three 3s were counted, so three are written. |
| 12 | Write | 4 | [1, 1, 0, 3, 2] | [0, 1, 3, 3, 3, 4, 4] | Push 4 twice; loop ends | value reaches maxValue, the last slot. The next check, 5 <= 4, ends the loop. |
It returns [0, 1, 3, 3, 3, 4, 4] after 7 counting steps and 5 slot visits: n + k = 7 + 5 steps, with zero comparisons between items.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Counting sort | O(n + k) | O(n + k) | Values are whole numbers in a small range, like ratings, ages, or letters, and k is not much bigger than n. |
| Radix sort | O(d × n) | O(n) | Values are whole numbers with a large range but few digits, such as 32-bit IDs. It runs counting sort once per digit. |
Comparison sort (built-in sort) | O(n log n) | O(n) | Values are arbitrary numbers, strings, or objects, or the range is far bigger than the list. |
Extra memory: O(n + k): counts has k slots and result has n items. The stable version for records also needs an O(n) output array.
How the steps grow
- Counting sort
O(n + k) - Radix sort
O(d × n) - Comparison sort (built-in
sort)O(n log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Counting sort | 20 | 2,000 | 2 million |
| Radix sort | 100 | 1 million | 1 trillion |
Comparison sort (built-in sort) | 33 | 9,966 | 20 million |
Inputs that need care
5 tricky inputs
- An empty list with
maxValue = 3 - The counting loop does nothing, the writing loop checks 4 empty slots, and the result is
[]. - A value bigger than
maxValue:[2, 6, 1]withmaxValue = 4 counts[6]++writes outside the slots the write loop visits, so6is silently dropped and the result is[1, 2]. ComputemaxValuewithMath.max(...items)if you aren't sure.- Negative values:
[3, -1, 2] counts[-1]is not an array slot, so-1is dropped too. Shift every value by the minimum (value - min) before counting and add it back when writing.- Decimals:
[2, 2.5, 1] 2.5isn't an index, so it disappears. Counting sort only works on whole numbers, or on keys you can map to whole numbers.- Records, like tasks with a
priorityfrom 0 to 3 - Writing out bare values loses the rest of each record. Use the stable placement version: turn
countsinto starting positions and copy each record to its slot.
Variations and follow-ups
Twists interviewers add
Stable counting sort with prefix sums
Turn counts into running totals so that each slot says where its value's first item goes. Then copy each item, in input order, to its position and move that position forward by one. Equal keys keep their order, which is exactly what radix sort needs for each digit.
Counting with an offset
For values from min to max, use max - min + 1 slots and index them with value - min. Temperatures from -20 to 45 need just 66 slots. It is the fix for negative numbers.
Counting letters
To sort the letters of a word, or check whether two words use the same letters, count into 26 slots, one per letter from a to z. It runs in O(n) for any word length.
Hash map counting
When the possible values are few but spread out, like [1000, 5, 1000, 7], count in a Map instead of an array, then sort only the distinct keys. That is O(n + d log d) for d distinct values.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from the range: "values are between 0 and 100", "lowercase letters only", "ages", "scores". A small, known range is the signal that you can beat
O(n log n). - Explain it as "use the value as an index". Say clearly that it is
O(n + k), and that it is only better thanO(n log n)whenkis small compared ton log n. - Test by hand with a value missing from the range, a value repeated, and the value
maxValueitself, to check the+ 1in the array size. - The usual follow-up is "how would you sort records, not just numbers, and keep it stable?". Answer with prefix sums: compute where each key starts, then place the records in input order.
Where frontend code uses it
The same idea in real UI code
- Sorting reviews by star rating, or tasks by a priority from 0 to 4, needs only a handful of counters.
- Histograms in dashboards are the counting step on its own: counts per bucket, drawn as bars.
- Grouping items into a few fixed lanes, such as a kanban board's columns, is the stable counting placement: each item goes into its lane, in its original order.