Counting and Radix Sort

Sort small whole numbers in O(n) by counting instead of comparing, then extend it digit by digit with radix sort. Ends with a table of every sort.

17 minLesson 5 of 7
On this page
  1. The O(n log n) wall
  2. The idea: count, then place
  3. Radix sort: one digit at a time
  4. Every sort in this module
  5. Common mistakes
  6. Check yourself
  7. What to practise next

You have 300 quiz papers, each scored from 0 to 10, and you need them in order. You could merge sort them. Or you could put 11 empty trays on the desk, labelled 0 to 10, drop each paper into the tray for its score, and then stack the trays in order. One look per paper, and no paper is ever compared with another.

That is counting sort. It beats every sort from the earlier lessons, but only because the scores are small whole numbers in a known range.

The O(n log n) wall

Merge sort, quicksort and heap sort all learn about the order by asking one question: is this item smaller than that one? Sorts that only ask that question can never do better than O(n log n) in the worst case.

Here is why. n items can be arranged in n × (n − 1) × … × 1 different orders, and the sort has to tell them all apart. Each comparison has two answers, so at best it cuts the possible orders in half. Halving that many orders down to one takes about n log₂ n comparisons. For 10 items there are 3,628,800 orders, and halving down to 1 takes at least 22 comparisons.

Counting sort steps around this wall: it never compares two items, and uses each key as an array index instead.

The idea: count, then place

Say every key is a whole number from 0 to k − 1.

  1. Count how many items have each key.
  2. Work out where each key starts in the output. Keys of 0 start at position 0. Keys of 1 start right after all the 0s, keys of 2 right after all the 0s and 1s, and so on. That is a running total of the counts, a prefix sum.
  3. Place each item, in input order, at its key's start position, and move that start position one step forward.

Step 3 walks the input from front to back, so items with equal keys land in the order they came in. So counting sort is stable, which radix sort will need.

In the trace below, the keys are 0 to 3, and the letter tells equal keys apart: 3a and 3b both have key 3, and 3a came first.

Counting sort with keys 0 to 3Use the arrows or ← → keys
for each item: count[key] += 1start[0] = 0for v from 1 to k - 1: start[v] = start[v - 1] + count[v - 1]for each item, in input order:  out[start[key]] = item  start[key] += 1
  1. input
    03a
    11a
    23b
    30a
    42a
    51b
    count
    00
    10
    20
    30

    1Six items with keys between 0 and 3, so k = 4. Start with four counters at zero.

  2. input
    03a
    11a
    23b
    30a
    42a
    51b
    count
    01
    12
    21
    32

    2One pass over the input. One item has key 0, two have key 1, one has key 2, two have key 3.

  3. input
    03a
    11a
    23b
    30a
    42a
    51b
    start
    00
    11
    23
    34
    out
    0
    1
    2
    3
    4
    5

    3Running totals give the start of each key. No key is below 0, so 0s start at 0. One item has a key below 1, so 1s start at 1. Three items have keys below 2, so 2s start at 3. Four are below 3, so 3s start at 4.

  4. input
    03ax
    11a
    23b
    30a
    42a
    51b
    start
    00
    11
    23
    35
    out
    0
    1
    2
    3
    43a
    5

    43a has key 3, which starts at 4. Put it at out[4], and move the start for 3 to 5.

  5. input
    03a
    11ax
    23b
    30a
    42a
    51b
    start
    00
    12
    23
    35
    out
    0
    11a
    2
    3
    43a
    5

    51a goes to out[1]. The start for 1 moves to 2.

  6. input
    03a
    11a
    23bx
    30a
    42a
    51b
    start
    00
    12
    23
    36
    out
    0
    11a
    2
    3
    43a
    53b

    63b goes to out[5], right after 3a, because the start for 3 already moved past it.

  7. input
    03a
    11a
    23b
    30ax
    42a
    51b
    start
    01
    12
    23
    36
    out
    00a
    11a
    2
    3
    43a
    53b

    70a goes to out[0].

  8. input
    03a
    11a
    23b
    30a
    42ax
    51b
    start
    01
    12
    24
    36
    out
    00a
    11a
    2
    32a
    43a
    53b

    82a goes to out[3].

  9. input
    03a
    11a
    23b
    30a
    42a
    51bx
    start
    01
    13
    24
    36
    out
    00a
    11a
    21b
    32a
    43a
    53b

    91b goes to out[2], right after 1a.

  10. out
    00a
    11a
    21b
    32a
    43a
    53b
    comparisons
    0

    10Sorted, with 1a still before 1b and 3a before 3b. Two passes over 6 items and one over 4 counters. Zero comparisons between items.

1 / 10

The code takes a key function that turns an item into its whole-number key, so it can sort records, not only bare numbers. Radix sort reuses it below.

Counting sort
// key(item) must be a whole number from 0 to k - 1
function countingSortBy(items, key, k) {
  const count = new Array(k).fill(0);
  for (const item of items) count[key(item)]++;
  const start = new Array(k).fill(0); // start[v] = items with key < v
  for (let v = 1; v < k; v++) start[v] = start[v - 1] + count[v - 1];
  const out = new Array(items.length);
  for (const item of items) {
    out[start[key(item)]++] = item; // input order: stable
  }
  return out;
}
 
countingSortBy([3, 1, 3, 0, 2, 1], (x) => x, 4); // [0, 1, 1, 2, 3, 3]
# key(item) must be a whole number from 0 to k - 1
def counting_sort_by(items, key, k):
    count = [0] * k
    for item in items:
        count[key(item)] += 1
    start = [0] * k  # start[v] = items with key < v
    for v in range(1, k):
        start[v] = start[v - 1] + count[v - 1]
    out = [None] * len(items)
    for item in items:  # input order: stable
        out[start[key(item)]] = item
        start[key(item)] += 1
    return out
 
counting_sort_by([3, 1, 3, 0, 2, 1], lambda x: x, 4)  # [0, 1, 1, 2, 3, 3]

Radix sort: one digit at a time

Counting sort needs one counter per possible key. For quiz scores that is 11 counters. For order numbers up to a billion, it would be a billion counters, which is hopeless.

Radix sort fixes that by sorting on one digit at a time. Each digit is 0 to 9, so each pass is a counting sort with just 10 counters. The surprising part is the order: start with the last digit (the ones), then the tens, then the hundreds.

Radix sort, ones digit firstUse the arrows or ← → keys
  1. nums
    0512
    1038
    2407
    3223
    4095
    5340
    6018

    1Seven numbers, padded to three digits so every digit position is visible. The largest is 512, so three passes are enough.

  2. by ones
    0340
    1512
    2223
    3095
    4407
    5038
    6018

    2Pass 1: stable sort by the ones digit. 340 ends in 0, so it goes first. 038 and 018 both end in 8 and keep their input order.

  3. by tens
    0407
    1512
    2018
    3223
    4038
    5340
    6095

    3Pass 2: stable sort by the tens digit. 512 and 018 both have a 1 there. 512 stays first because pass 1 put it first, and its ones digit, 2, is smaller than 8.

  4. by tens
    0407
    1512
    2018
    3223
    4038
    5340
    6095

    4Look at the last two digits now: 07, 12, 18, 23, 38, 40, 95. They are in order. After pass p, the numbers are sorted by their last p digits.

  5. by hundreds
    0018
    1038
    2095
    3223
    4340
    5407
    6512

    5Pass 3: stable sort by the hundreds digit. 018, 038 and 095 all have 0 there, so stability keeps the order the earlier passes gave them.

  6. sorted
    0018
    1038
    2095
    3223
    4340
    5407
    6512
    passes
    3
    counters per pass
    10

    6Fully sorted after 3 passes of 7 items each, and every pass used only 10 counters.

1 / 6

Here is why it works. The last pass decides by the most important digit. When two numbers tie on that digit, stability leaves them in the order the earlier passes chose, and that order was already correct for the lower digits. Each pass keeps all the work of the passes before it.

Radix sort
// non-negative integers; uses countingSortBy from above
function radixSort(nums) {
  const biggest = nums.reduce((m, x) => Math.max(m, x), 0);
  for (let place = 1; place <= biggest; place *= 10) {
    nums = countingSortBy(nums, (x) => Math.floor(x / place) % 10, 10);
  }
  return nums;
}
 
radixSort([512, 38, 407, 223, 95, 340, 18]);
// [18, 38, 95, 223, 340, 407, 512]
# non-negative integers; uses counting_sort_by from above
def radix_sort(nums):
    biggest = max(nums, default=0)
    place = 1
    while place <= biggest:
        nums = counting_sort_by(nums, lambda x: x // place % 10, 10)
        place *= 10
    return nums
 
radix_sort([512, 38, 407, 223, 95, 340, 18])
# [18, 38, 95, 223, 340, 407, 512]
ApproachTimeSpaceWhy
Counting sortO(n + k)O(n + k)Passes over the n items and over the k counters. Great when k is not much bigger than n.
Radix sortO(d × (n + b))O(n + b)d digits, each a counting sort with b counters. Base 10 means b = 10.

Every sort in this module

Here n is the number of items, k the number of possible keys, d the number of digits and b the base.

SortBestAverageWorstExtra spaceStableReach for it when
InsertionO(n)O(n²)O(n²)O(1)Yesthe input is tiny or nearly sorted
SelectionO(n²)O(n²)O(n²)O(1)Nowrites are very expensive (at most n − 1 swaps)
BubbleO(n)O(n²)O(n²)O(1)Yesalmost never; it is a teaching example
MergeO(n log n)O(n log n)O(n log n)O(n)Yesyou need stability, a linked list, or data on disk
QuicksortO(n log n)O(n log n)O(n²)O(log n), O(n) worstNofast in-place sorting with a random pivot
HeapO(n log n)O(n log n)O(n log n)O(1)Noyou need a guarantee with no extra memory
CountingO(n + k)O(n + k)O(n + k)O(n + k)Yeskeys are small whole numbers
RadixO(d(n + b))O(d(n + b))O(d(n + b))O(n + b)Yeskeys are fixed-length numbers or strings
Built-in (Timsort)O(n)O(n log n)O(n log n)O(n)Yesalmost always, in real code

Best cases assume distinct keys. With all keys equal, heap sort takes O(n) and the quicksort from its lesson takes O(n²).

In real code, call the built-in sort unless the right-hand column gives you a reason not to.

Common mistakes

Check yourself

Quick check

Counts for keys 0 to 3 are [2, 0, 3, 1]. At which output position does the first item with key 3 go?

Radix sort on [21, 12, 22, 11]. What is the order after the ones pass?

You must sort 1 million user IDs, each a random number up to 1018. Is counting sort a good fit?

What to practise next

Sorting is often a first step. Once data is sorted, you can find anything in it in O(log n) with Binary Search, the next lesson.