Non-Comparison Sort | LSD/MSD | Digit-by-Digit Stable Passes
Imagine sorting a big stack of mail by ZIP code without ever comparing two envelopes to each other. You make ten trays labelled 0 through 9. First you deal every envelope into the tray matching its last ZIP digit, then stack the trays back up in order. You repeat with the next-to-last digit, then the next, and so on. After the last digit, the whole pile is sorted. That trick — sort by one digit at a time, never comparing whole numbers — is Radix Sort.
The quantities that matter are the count of items $n$, the number of digit positions $d$, and the number of buckets $k$ (the radix, which is $10$ for ordinary decimal numbers and $256$ if you treat each byte as a "digit"). One rule drives everything: in each pass you do a single stable bucket sort on one digit, working from the least-significant digit to the most.
Why "stable" is non-negotiable: when two numbers share the current digit, the pass must keep the order an earlier pass already gave them. If the per-pass sort reshuffled ties, every earlier pass's work would be undone. Counting Sort is the usual engine because it is both stable and $O(n+k)$. With $d$ passes the total cost is $O(d(n+k))$ — linear in $n$ when $d$ and $k$ are fixed, which is how Radix Sort can beat the $O(n\log n)$ floor that binds comparison sorts. The sliders map straight onto the theory: Array Size sets $n$, and Digits sets $d$ (so it also sets how many passes you watch).
Try this in the sim above. First, set Digits to 1 and step through — the array sorts in a single pass, because one digit is all there is. Next, raise Digits to 3 and watch the pink-highlighted column march right-to-left, the array only becoming truly sorted on the final pass. Finally, choose the Many Duplicates preset and step slowly: equal values never swap order, which is the stability property working in front of you.
| Symbol | Meaning |
|---|---|
| n | Number of elements |
| d | Number of digits (or bits per chunk) |
| k | Base/radix (10 for decimal, 256 for bytes) |
| b | Total bits in a number |
❌ "Radix Sort is O(n) so it's always faster than O(n log n) sorts."
✅ Radix Sort is O(d·n) where d can be large. For 64-bit integers with base 10, d=20. Constant factors and cache effects matter. For small n, comparison sorts win.
❌ "Radix Sort can be done with any sorting algorithm per pass."
✅ The per-pass sort MUST be stable. Using an unstable sort (like Heapsort or standard Quicksort) would produce incorrect results.
❌ "MSD Radix Sort is always better than LSD because it processes the most important digits first."
✅ LSD is simpler (iterative), more cache-friendly, and sufficient for fixed-length keys. MSD is needed for variable-length strings and some in-place variants.
❌ Using modulo and division naively for large bases (e.g., base 1000) — risk of integer overflow.
✅ Prefer base 256 (byte-level) or 16 (nibble-level). Extract with bitwise ops: digit = (val >> (8*pass)) & 0xFF. Faster and overflow-safe.
❌ Forgetting to copy output buffer back to input for next pass.
✅ After each Counting Sort pass, the sorted output is in a temporary buffer B. Before the next pass, copy B back to A (or ping-pong between two buffers to avoid copies).
❌ Using d = number of digits of max value but not padding shorter numbers.
✅ For LSD, shorter numbers have implicit leading zeros — extracting digit i from a number shorter than i digits correctly gives 0. No explicit padding needed in implementation.