Counting Sort
Topic type: Technique-Based — a non-comparison integer sorting technique that distributes elements into indexed frequency buckets instead of comparing them.
LeetCode has fewer than 50 problems tagged Counting Sort directly. Depth: core algorithms, main patterns, key complexities.
See sorting.md for the full comparison of all sorting algorithms and when to choose between them.
Core Concepts
Counting sort — a non-comparison sort for integers (or values mappable to non-negative integers) in a bounded range [0, k). Achieves O(n + k) time by tallying frequency of each value, then reconstructing the sorted output from tallies.
Frequency array (count array) — an auxiliary array count of size k where count[v] holds how many times value v appears in the input. This is the core data structure of counting sort.
Prefix-sum step — transform count[v] from a frequency to a cumulative count: count[v] = number of elements ≤ v. This step converts the frequency array into a positional map, enabling stable placement of each element into the output.
Stability — equal elements appear in the original relative order in the output. Counting sort is stable when the output-filling loop traverses the input right-to-left and places each element at count[v] - 1, then decrements count[v]. Traversing left-to-right produces an unstable but still correct sort.
Value range k — the difference between maximum and minimum possible values (after offset normalisation). Counting sort is only practical when k = O(n); if k >> n, the count array dominates both time and space.
Offset normalisation — when values are not in [0, k) (e.g., they start at some minimum m), subtract m from every value before indexing into the count array, making the range [0, max - min].
Structural Invariants
| Invariant | What maintains it | What breaks if violated |
|---|---|---|
count array size = k (covers every possible value) | Caller allocates with k = max_val - min_val + 1 | Index-out-of-bounds or silently missed values |
After prefix-sum step: count[v] = number of elements ≤ v (after offset) | Prefix-sum pass over count | Elements are placed at wrong output positions |
| Output-filling loop traverses input right-to-left for stability | Iteration direction | Stable order broken; equal elements reverse relative position |
| Offset applied consistently to both indexing and reconstruction | Caller's responsibility | Elements written to wrong output positions or out of bounds |
Types / Variants
| Variant | What it is | When to use | Key difference |
|---|---|---|---|
| Basic (unstable) | Fill output from count array directly, no prefix-sum step | When only sorted values are needed, not original records | Simpler; O(n + k) time and O(k) space; loses identity of original elements |
| Stable counting sort | Prefix-sum step + right-to-left output fill | When original objects must be reordered (e.g., as a subroutine of radix sort) | Requires O(n) output array; preserves relative order of equals |
| Offset counting sort | Subtract min_val to normalise range to [0, k) | Values are integers but do not start at 0 (e.g., temperatures −50 to +50) | Same algorithm; only allocation size and index formula change |
| In-place reconstruction | After building count, overwrite the original array by expanding each count | Input is a flat array of integers and no record identity is needed | O(1) extra space (beyond count); not stable |
Key Algorithms
Basic counting sort (in-place reconstruction)
Tally each value into count, then overwrite the original array by writing each value v exactly count[v] times in order. — O(n + k) time, O(k) space.
count = [0] * (max_val + 1)
for v in arr: count[v] += 1
arr[:] = [v for v in range(max_val + 1) for _ in range(count[v])]
This variant is not stable — it does not preserve original element identity, only the multiset of values.
Stable counting sort
Three passes: (1) build frequency array, (2) prefix-sum to get positions, (3) fill output right-to-left so equal elements retain input order. — O(n + k) time, O(n + k) space.
for v in arr: count[v - offset] += 1 # pass 1: frequencies
for i in range(1, k): count[i] += count[i-1] # pass 2: prefix sums
for v in reversed(arr): # pass 3: place right-to-left
out[count[v - offset] - 1] = v; count[v - offset] -= 1
The right-to-left pass ensures the last occurrence of a duplicate is placed last — matching input order.
Counting sort as a frequency histogram query
Build count once; answer multiple "how many elements equal v" or "how many elements in [a, b]" queries in O(1) using count[v] or prefix[b] - prefix[a-1]. — O(n + k) build, O(1) per query.
This shifts the use case from sorting to frequency analysis — common in character-frequency and range-counting problems.
Radix sort digit pass (counting sort subroutine)
Radix sort applies stable counting sort once per digit position (LSD to MSD). Each pass sorts by one digit while preserving relative order from prior passes. — O(nk) total where k = number of digits.
See sorting.md for full radix sort coverage.
Problem Taxonomy
By Problem Category
| Category | What it asks | Key technique |
|---|---|---|
| Sort integers in small range | Return sorted array; values bounded by constraint | Basic counting sort or in-place reconstruction |
| Frequency-based reordering | Sort by frequency, then by value; "sort characters by frequency" | Build freq map, sort freq keys, expand back |
| Character anagram / permutation check | Do two strings have identical character frequencies? | Build two count arrays of size 26; compare |
| Relative sort / custom order | Sort array A so elements appear in order given by array B | Build position map from B; counting sort on positions |
| Majority element / dominant value | Find value appearing more than n/k times | Frequency array; scan for threshold |
| Range value counting | "How many elements in [l, r] equal x?" or "how many fall in [a, b]?" | Frequency array + prefix sum over values |
| Reconstruct array from frequency constraints | Given frequency map, reconstruct a valid array | Expand each (value, count) pair in sorted order |
Problem Signal → Technique
| Signal in problem | Likely technique |
|---|---|
"values in range [0, k]" with small k | Counting sort or frequency array |
| "characters in lowercase English letters" | Count array of size 26 |
| "sort by frequency" | Frequency map + sort on frequency key |
| "anagram", "permutation of string" | Build and compare char-count arrays |
| "relative sort", "custom sort order" | Counting sort with position map |
| "minimum number of swaps / moves to sort" | Counting sort to find expected position of each element |
| "sort nearly-sorted integers" | Counting sort if range is small; otherwise insertion sort |
| "top k frequent elements" | Frequency array + bucket sort on frequency index |
Common Patterns
| Pattern | When it applies | Mechanism |
|---|---|---|
| Size-26 char array | Any single-string frequency problem | count = [0]*26; index by ord(c) - ord('a') |
| Offset normalisation | Values span a range not starting at 0 | count[v - min_val] += 1; allocate max_val - min_val + 1 slots |
| Frequency bucket indexed by count | "Top k frequent" / "sort by frequency" | Build bucket[freq].append(val); read from highest freq down |
| Two-pass stable sort | Radix sort digit pass; any sort where order of equals matters | Pass 1: prefix sums on count; Pass 2: right-to-left output fill |
| Count array as presence set | "Find missing" / "find duplicate" in [1, n] | Increment count[v]; scan for 0 (missing) or 2 (duplicate) |
| Counting sort for comparison-free merge | Merging k sorted lists where values are bounded integers | Build unified count array from all lists; reconstruct once |
Edge Cases & Pitfalls
-
k much larger than n. If values can be up to 10^9 but n is only 10^3, allocating a count array of size 10^9 causes OOM. Fix: compress values to
[0, n)via coordinate compression before sorting, or use a hash map instead of an array. -
Forgetting offset when values are negative or start above 0. Indexing
count[v]directly whenvcan be negative causes an index error or silent wrong placement. Always computeoffset = min(arr)and index ascount[v - offset]. -
Off-by-one in prefix-sum step. The loop
for i in range(1, k): count[i] += count[i-1]must start at index 1, not 0. Starting at 0 corruptscount[0]. -
Right-to-left vs. left-to-right in output fill. Left-to-right fill is simpler but unstable. If the sort is used as a subroutine (e.g., in radix sort), left-to-right silently breaks correctness because equal-digit elements no longer preserve the order from the previous pass.
-
Not re-initialising count between radix passes. Reusing the same count array across digit passes without zeroing it out produces wrong positional offsets. Always reset or re-allocate between passes.
-
Empty input.
min(arr)andmax(arr)raiseValueErroron an empty list. Guard withif not arr: return arr. -
All-identical values. Count array has a single non-zero entry. Output fill must still iterate all k positions; this is correct but inefficient if k >> 1. Not a bug, but worth noting for constant-factor performance.
-
Modifying
arrwhile iterating in the output-fill pass. In-place reconstruction overwritesarrwhile reading it. The safe version expandscountdirectly without readingarragain; the stable version always uses a separate output array.
Implementation Notes
- Python's
collections.Counterbuilds a frequency map in O(n) but is backed by a hash map, not an array. For small fixed alphabets (size 26 or a known small k), a plain list is faster. [0] * kis faster thancollections.defaultdict(int)for dense integer ranges; use adefaultdictonly when the range is sparse or unknown.itertools.accumulateapplies the prefix-sum step in one line:count = list(accumulate(count)).- Python lists are unbounded integers so there is no overflow risk in the count array itself, but be cautious when porting to Java/C++ with
intcount arrays over large n. - For the size-26 character pattern,
ord(c) - ord('a')is the canonical index; do not useord(c)directly (indices 97–122 would waste the first 97 slots).
Cross-Topic Relations
| Related topic | Connection |
|---|---|
| Sorting | Counting sort is one entry in the full sorting taxonomy; radix and bucket sort are closely related non-comparison sorts. See sorting.md |
| Prefix Sum | The prefix-sum step inside stable counting sort is a direct application of prefix sums over the count array. See prefix-sum.md |
| Hash Table | When k is too large for an array, replace the count array with a hash map; same algorithm, different storage. See hash-table.md |
| Bucket Sort | Bucket sort generalises counting sort to non-integer or non-uniform ranges by grouping values into buckets; counting sort is the special case where each bucket holds exactly one value |
| Radix Sort | Uses stable counting sort as its inner digit-sort subroutine; correctness of radix sort depends entirely on counting sort being stable |
| Two Pointers | Combined with a frequency array, two pointers can enforce constraints like "at most k distinct values" by tracking live counts in O(n). See two-pointers.md |
| Sliding Window | Frequency array maintained under a sliding window tracks character or value distribution; counting sort logic provides the underlying frequency structure. See sliding-window.md |
Interview Reference
Must-Know Problems
Frequency and character sorting
- Sort Characters By Frequency (LC 451)
- Relative Sort Array (LC 1122)
- Sort Array by Increasing Frequency (LC 1636)
Anagram and permutation checks
- Valid Anagram (LC 242)
- Find All Anagrams in a String (LC 438)
- Permutation in String (LC 567)
Frequency-based analysis
- Top K Frequent Elements (LC 347) — frequency buckets indexed by count
- Top K Frequent Words (LC 692)
- Sort Colors (LC 75) — three-way partition; degenerate counting sort with k = 3
Missing / duplicate in bounded range
- Find All Numbers Disappeared in an Array (LC 448)
- Find the Duplicate Number (LC 287)
Harder applications
- Maximum Gap (LC 164) — bucket sort / pigeonhole; O(n) non-comparison sort
- H-Index (LC 274) — build citation count histogram; scan from right
Additional LeetCode Coverage
- Height Checker (LC 1051)
Clarification Questions to Ask
- What is the range of values? (Determines k and whether counting sort is feasible vs. coordinate-compressed or hash-map-based.)
- Are values guaranteed to be integers? (Counting sort does not apply to floats or arbitrary objects without a key function.)
- Does the problem require a stable sort, or is value order sufficient? (Unstable in-place reconstruction is simpler; stable requires an output array.)
- Can values be negative? (Requires offset normalisation; confirm whether min is known statically or must be computed.)
- Are there duplicate values, and does duplicate order matter? (Distinguishes stable from unstable variant.)
Common Interview Mistakes
- Allocating the count array as size
max_valinstead ofmax_val - min_val + 1, causing out-of-bounds when the minimum is non-zero. - Performing the prefix-sum pass left-to-right correctly but then filling the output left-to-right, producing an unstable result and breaking radix sort.
- Using counting sort when k is on the order of 10^9 without coordinate compression, leading to OOM on otherwise straightforward problems.
- Reusing the count array across multiple radix passes without zeroing it, producing wrong sorted output on the second digit.
- Applying counting sort to floating-point values by truncating to integers, silently losing precision and misplacing elements.
Typical Follow-Up Escalations
- "Now values can be up to 10^9." → Coordinate-compress values to
[0, n)first, or switch to comparison-based sort; counting sort is no longer practical. - "Now sort the array in O(n) with O(1) extra space." → Three-way partition (Dutch National Flag) if k = 3; otherwise counting sort's O(k) space is unavoidable for general k.
- "Now also sort by a secondary key when values are equal." → Use stable counting sort; the secondary key is preserved by right-to-left output fill.
- "Now support online insertions between sorts." → Counting sort is a batch algorithm; switch to a balanced BST or sorted container for online updates.
- "What if duplicates should be removed?" → Skip the prefix-sum and output steps; a single pass over
countyields the deduplicated sorted values.
Read Next
Start reading to get personalized recommendations
Explore Unread
Great job! You've read all available articles