Radix Sort

Sorting
Stable
avg O(nk)

Radix sort sorts numbers without ever comparing two of them - it sorts by digits instead. First pass: group everything by the last digit. Second pass: by the tens digit. Keep going until the biggest number runs out of digits. The surprise is that this works at all - and it works because each pass is stable, so the order created by earlier passes is never destroyed.

Watch it sort

Press play - or drag the timeline and step through it yourself.
Digit pass 1Bucketed 0Writes 0Step 1 / 116

18 values, biggest is 903 (3 digits). Sort by the last digit first, then the next, 3 passes in total.

reading digit writing back done

Complexity

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

Sorting library cards by call number

Librarians used to sort thousands of cards with a trick: first deal all cards into 10 piles by the *last* digit, stack the piles up in order, then deal again by the second-to-last digit, and so on. After the final deal, the cards are perfectly sorted. The magic rule: when dealing, never change the order of cards inside a pile. Each round of dealing respects all the work the earlier rounds did.

How it works, step by step

  1. Find the biggest number - its digit count says how many passes you need.

  2. Pass 1: put every number into one of ten buckets based on its last digit. Keep the order in which numbers arrive in each bucket.

  3. Empty the buckets back into the array in order: bucket 0 first, then bucket 1, up to bucket 9.

  4. Pass 2: same thing, but bucket by the tens digit. Numbers with the same tens digit stay in the order the last pass gave them - that is the stability doing its job.

  5. Repeat for the hundreds digit, thousands digit... one pass per digit.

  6. After the pass on the highest digit, the array is sorted. Total work: digits × (n + 10).

The code, in JavaScript

LSD (least significant digit) radix sort with array buckets. `flat()` empties the buckets in order - that step is the stable rebuild.

javascript
function radixSort(arr) {
  if (arr.length === 0) return [];
  let a = [...arr];

  const max = Math.max(...a);

  // One pass per digit: 1s, 10s, 100s...
  for (let div = 1; div <= max; div *= 10) {
    const buckets = Array.from({ length: 10 }, () => []);

    for (const value of a) {
      const digit = Math.floor(value / div) % 10;
      buckets[digit].push(value);      // push keeps arrival order → stable
    }

    a = buckets.flat();                // empty buckets 0..9 in order
  }

  return a;
}

radixSort([170, 45, 75, 2]);           // → [2, 45, 75, 170]

Dry run: sorting [170, 45, 75, 2]

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepDigit passArrayWhat happened
11[170, 45, 75, 2]170 → digit 0 → bucket 0.
21[170, 45, 75, 2]45 → digit 5 → bucket 5.
31[170, 45, 75, 2]75 → digit 5 → bucket 5.
41[170, 45, 75, 2]2 → digit 2 → bucket 2.
51[170, 45, 75, 2]Empty the buckets in order: write 170 into slot 1.
61[170, 2, 75, 2]Empty the buckets in order: write 2 into slot 2.
71[170, 2, 45, 2]Empty the buckets in order: write 45 into slot 3.
81[170, 2, 45, 75]Empty the buckets in order: write 75 into slot 4.
92[170, 2, 45, 75]170 → digit 7 → bucket 7.
102[170, 2, 45, 75]2 → digit 0 → bucket 0.
112[170, 2, 45, 75]45 → digit 4 → bucket 4.
122[170, 2, 45, 75]75 → digit 7 → bucket 7.
132[2, 2, 45, 75]Empty the buckets in order: write 2 into slot 1.
142[2, 45, 45, 75]Empty the buckets in order: write 45 into slot 2.
152[2, 45, 170, 75]Empty the buckets in order: write 170 into slot 3.
162[2, 45, 170, 75]Empty the buckets in order: write 75 into slot 4.
173[2, 45, 170, 75]2 → digit 0 → bucket 0.
183[2, 45, 170, 75]45 → digit 0 → bucket 0.
193[2, 45, 170, 75]170 → digit 1 → bucket 1.
203[2, 45, 170, 75]75 → digit 0 → bucket 0.
213[2, 45, 170, 75]Empty the buckets in order: write 2 into slot 1.
223[2, 45, 170, 75]Empty the buckets in order: write 45 into slot 2.
233[2, 45, 75, 75]Empty the buckets in order: write 75 into slot 3.
243[2, 45, 75, 170]Empty the buckets in order: write 170 into slot 4.

Good choice when…

  • You are sorting many integers with a bounded number of digits - IDs, timestamps, zip codes. d passes of O(n) beat O(n log n) when d is small.
  • Keys are fixed-length strings (codes, hashes) - radix sort handles them in linear time.
  • You need stability and speed together on integer keys - each pass is a stable counting sort.
  • n is huge and comparisons are the bottleneck: radix does arithmetic, not comparisons.

Bad choice when…

  • Numbers can be arbitrarily long - the digit count d becomes log(max), and the advantage over O(n log n) evaporates.
  • Keys are floats, negatives, or variable-length strings - possible, but the extra handling usually erases the win. A comparison sort is simpler.
  • n is small. The bucket bookkeeping costs more than just calling a good comparison sort.

Common mistakes

  • Each pass must be stable - that is the entire trick. Use counting sort or ordered buckets per pass; sneak in an unstable pass and the earlier digits' order is destroyed silently.
  • Start from the least significant digit (LSD). Going most-significant-first also exists, but then you must sort each bucket recursively - a different, more complex algorithm.
  • Negative numbers break the digit math (`-5 % 10` is `-5` in JavaScript). Offset all values to be non-negative first, or split into negative/positive halves.
  • The complexity is O(d·(n + b)), not "O(n) always". For 64-bit random numbers d is ~20 decimal digits - measure before assuming radix wins.

Radix Sort vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
Radix Sortthis pageO(nk)O(nk)O(nk)O(n + k)yes
Counting SortO(n + k)O(n + k)O(n + k)O(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