DSA Counting Sort

Counting sort sorts a collection of integers by counting how many times each value occurs, avoiding comparisons between elements entirely.

Why Counting Sort Is Different

Every algorithm covered so far — insertion sort, quick sort, and merge sort — decides ordering by comparing pairs of elements. Counting sort takes a different approach: it counts how many times each distinct value appears in the input, then uses those counts to compute exactly where each value belongs in the sorted output. Because it never compares two elements directly, it can beat the O(n log n) lower bound that applies to comparison-based sorts.

  • Find the range of values in the input, from the minimum to the maximum.
  • Create a counts array with one slot per possible value, initialized to zero.
  • Scan the input once, incrementing the count for each value seen.
  • Convert counts into cumulative counts to know each value's final position.
  • Build the sorted output by placing each input element at its computed position.

Example

def counting_sort(arr):
    if not arr:
        return arr
    maximum = max(arr)
    counts = [0] * (maximum + 1)
    for num in arr:
        counts[num] += 1
    result = []
    for value, count in enumerate(counts):
        result.extend([value] * count)
    return result

print(counting_sort([4, 2, 2, 8, 3, 3, 1]))

When Counting Sort Applies

Counting sort only works well when the values being sorted are integers (or can be mapped to small integers) drawn from a range that is not much larger than the number of elements. It is commonly used for sorting exam scores, ages, small categorical codes, or as a stable subroutine inside radix sort for sorting larger numbers digit by digit.

Example

def counting_sort_stable(arr, key=lambda x: x):
    if not arr:
        return arr
    max_key = max(key(x) for x in arr)
    counts = [0] * (max_key + 1)
    for item in arr:
        counts[key(item)] += 1
    for i in range(1, len(counts)):
        counts[i] += counts[i - 1]
    output = [None] * len(arr)
    for item in reversed(arr):
        k = key(item)
        counts[k] -= 1
        output[counts[k]] = item
    return output

records = [('apple', 3), ('banana', 1), ('cherry', 3), ('date', 1)]
print(counting_sort_stable(records, key=lambda r: r[1]))
Note: Counting sort is unsuitable when the value range k is much larger than the number of elements n — for example, sorting ten numbers ranging from 0 to ten million would allocate an enormous, mostly-empty counts array.

Complexity Analysis

CaseTime ComplexitySpace Complexity
BestO(n + k)O(n + k)
AverageO(n + k)O(n + k)
WorstO(n + k)O(n + k)

Example

def sort_string_chars(text):
    counts = [0] * 26
    for ch in text:
        counts[ord(ch) - ord('a')] += 1
    result = []
    for i, count in enumerate(counts):
        result.append(chr(i + ord('a')) * count)
    return ''.join(result)

print(sort_string_chars('banana'))
Note: Because counting sort is stable and non-comparison-based, it is the standard building block for radix sort, which repeatedly counting-sorts numbers by one digit at a time, from least to most significant.

Exercise: DSA Counting Sort

What is the core idea behind counting sort?