CMD Guide
HomeDSAAdvanced Patterns

Counting Sort Algorithm

Counting Sort

Counting sort avoids comparisons altogether: it counts how many times each key value occurs, converts those counts into cumulative counts ("how many elements are ≤ this value"), and uses that cumulative count directly as the final index of each element — turning sorting into an array-indexing problem instead of an ordering problem.

Recognize the pattern

Brute force → optimal

Brute force: a comparison sort (merge sort, quicksort) treats elements as opaque and only asks "is a < b?". That costs O(n log n) comparisons and time, ignoring the fact that the values are small bounded integers.

Optimal (counting sort): tally frequencies in an array indexed by value, prefix-sum the tallies into placement offsets, then place each element once using those offsets. Cost drops to O(n + k) time at the price of O(k) extra space — it only applies to bounded integer keys, not general comparable objects.

Complexity, derived from first principles

Let n = number of elements. The Java implementation below assumes non-negative keys and sizes the count array as max + 1 with no min-offset, so for that code k = max + 1 — the value itself is the index. (The offset variant shown in Pitfalls instead uses k = max−min+1; the two are different implementations with different k, not the same code with two derivations.)

Total time = O(n) + O(k) + O(n) + O(k) + O(n) + O(n) = O(n + k). This beats the O(n log n) comparison lower bound because counting sort never compares two elements — the lower bound only binds algorithms that make ordering decisions via comparisons.

Space: count array O(k), output array O(n) → O(n + k) auxiliary space (not in-place). When k ≫ n, the count array dominates and the algorithm becomes worse than O(n log n). This is why an input like [100000, 100001, 100002] is dangerous for the plain implementation below: it allocates new int[100003] and runs in O(n + max), not O(n + range) — the range is only 3, but nothing in that code exploits it. Only the offset variant in Pitfalls achieves O(n + (max−min+1)) on such input.

Traced example

Input: [4, 2, 2, 8, 3, 3, 1], n = 7, max = 8, so count array has size 9 (indices 0..8).

StepState
Tallycount = [0,1,2,2,1,0,0,0,1] (index i holds how many times value i appears: 1→1, 2→2, 3→2, 4→1, 8→1)
Prefix sumcount = [0,1,3,5,6,6,6,6,7] (count[v] = number of elements ≤ v = last output index for value v)

Placing elements by scanning the input right-to-left (this is what preserves stability):

ivalactionoutput aftercount after
61place at count[1]-1=0[1,_,_,_,_,_,_]count[1]→0
53place at count[3]-1=4[1,_,_,_,3,_,_]count[3]→4
43place at count[3]-1=3[1,_,_,3,3,_,_]count[3]→3
38place at count[8]-1=6[1,_,_,3,3,_,8]count[8]→6
22place at count[2]-1=2[1,_,2,3,3,_,8]count[2]→2
12place at count[2]-1=1[1,2,2,3,3,_,8]count[2]→1
04place at count[4]-1=5[1,2,2,3,3,4,8]count[4]→5

Final sorted output: [1, 2, 2, 3, 3, 4, 8]. Note the two 2's (input indices 2,1) and two 3's (input indices 5,4) retained their original relative order — that is stability, produced by scanning the input right-to-left.

Java implementation

static int[] countingSort(int[] arr) {
    if (arr.length == 0) return arr;
    int max = arr[0];
    for (int v : arr) max = Math.max(max, v);
    int[] count = new int[max + 1];
    for (int v : arr) count[v]++;
    for (int i = 1; i <= max; i++) count[i] += count[i - 1]; // cumulative
    int[] output = new int[arr.length];
    for (int i = arr.length - 1; i >= 0; i--) {   // reverse scan => stable
        output[count[arr[i]] - 1] = arr[i];
        count[arr[i]]--;
    }
    return output;
}

This version only handles non-negative keys and its cost is O(n + max), because it never subtracts a min offset — see the offset variant below for the O(n + (max−min+1)) version.

Pitfalls

When to use / when not — trade-offs vs Radix Sort

Use counting sort when keys are integers with a range k = O(n) (or smaller), you need stability, and you want guaranteed linear time regardless of input order (unlike quicksort's O(n²) worst case).

Avoid it when the key range is large relative to n, keys aren't integers/discretizable, or memory is tight (O(k) auxiliary space is non-negotiable).

vs. Radix Sort: radix sort decomposes a large-range integer (or string) key into fixed-width digits and runs counting sort once per digit, achieving O(d·(n+b)) for d digits of base b — this is the right choice when k is too large for one counting-sort pass but keys have bounded digit-length (e.g. 32-bit ints, fixed-length strings). Counting sort is radix sort's stable building block, not its competitor.

vs. comparison sorts (merge/quicksort): comparison sorts handle arbitrary orderable types and use O(n) extra space (merge sort) or O(log n) (quicksort), at O(n log n) time; counting sort trades that generality for O(n+k) time when its preconditions hold.

Takeaways

Recall question

Given the count array after the cumulative-sum step, why does traversing the input right-to-left (rather than left-to-right) preserve the relative order of equal keys in the output?


Synthesized and deepened from the original page source and standard CLRS-style counting sort treatment.

🤖 Don't fully get this? Learn it with Claude

Stuck on Counting Sort Algorithm? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Counting Sort Algorithm** (DSA) and want to truly understand it. Explain Counting Sort Algorithm from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Counting Sort Algorithm** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Counting Sort Algorithm** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Counting Sort Algorithm** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes