Bucket Sort
Distribute elements into buckets by value range, sort each bucket, and concatenate — linear on uniform data.
Overview
Bucket sort assumes the keys are drawn roughly uniformly from a known range (classically reals in [0, 1)). It creates k ≈ n buckets, drops each element into the bucket for its sub-range, sorts each bucket with a simple sort (Insertion Sort), and concatenates. Under the uniformity assumption each bucket holds O(1) elements on average, giving expected O(n) time.
It is stable if the per-bucket sort is stable and elements are appended in input order, and it needs O(n + k) extra space. It generalizes Counting Sort (buckets hold one key each) and is related to Radix Sort (buckets by digit). Its worst case is O(n²) when all elements land in one bucket, so it is a poor choice for skewed or adversarial inputs.
Intuition
A mental model before the formal terms.
Sorting a stack of forms by surname: first toss each into one of 26 trays labelled A–Z, then sort each tray by hand (each is small), then stack the trays in order. If half the surnames start with "S", the S tray takes most of the work — that is the uniformity assumption failing.
How it works
- Choose the number of buckets
k(typicallyn) and a mapping from value to bucket index, e.g.⌊k · (x - min) / (max - min + ε)⌋. - Scan the input and append each element to its bucket (preserving input order).
- Sort each bucket, usually with Insertion Sort since buckets are expected to be tiny (or recursively with bucket sort).
- Concatenate the buckets in index order.
Why it works
The mapping is monotonic: every element in bucket i is ≤ every element in bucket i + 1. So sorted buckets concatenated in order are globally sorted.
With n elements uniformly spread over n buckets, the expected sum of squared bucket sizes is O(n), so total insertion-sort work is expected O(n) (CLRS §8.4).
Recognition
How to tell a problem wants this.
- Values are floats or reals uniformly distributed in a known interval.
- The problem hints at "linear time" with values in a bounded range but too many distinct values for Counting Sort.
- Maximum gap / "sort by frequency" problems where bucketing by a derived key avoids a full sort.
Interactive visualization
Play, step, change the input. ← → and space work too.
1k = number of buckets; lo = min(a); hi = max(a)2for x in a:3 b = floor((x - lo) / (hi - lo + 1) * k)4 buckets[b].append(x)5for each bucket: insertionSort(bucket)6a = concat(buckets)Pseudocode
1k = n; buckets = [[] for _ in range(k)]2for x in a: buckets[floor(k * (x - mn) / (mx - mn + eps))].append(x)3for each bucket: insertionSort(bucket)4return concat(buckets)Implementations
1def bucket_sort(a: list[float]) -> list[float]:21 · Handle trivial input and find the value range3 n = len(a)4 if n <= 1:5 return a[:]6 mn, mx = min(a), max(a)7 if mn == mx:8 return a[:]92 · Distribute elements into n buckets by sub-range10 buckets: list[list[float]] = [[] for _ in range(n)]11 width = (mx - mn) / n12 for x in a:13 idx = min(n - 1, int((x - mn) / width))14 buckets[idx].append(x)153 · Insertion-sort each bucket (stable)16 for b in buckets:17 for i in range(1, len(b)):18 key = b[i]19 j = i - 120 while j >= 0 and b[j] > key:21 b[j + 1] = b[j]22 j -= 123 b[j + 1] = key244 · Concatenate buckets in index order25 out: list[float] = []26 for b in buckets:27 out.extend(b)28 return out- The guards return
a[:](a shallow copy) so callers never receive the input list aliased. [[] for _ in range(n)]builds n independent lists;[[]] * nwould alias one list n times.int((x - mn) / width)truncates toward zero (safe here because the offset is non-negative), andmin(n - 1, ...)clampsmxinto the last bucket.- The explicit insertion sort keeps the example self-contained and stable; the strict
>never moves equal values past each other. out.extend(b)appends each bucket in O(len(b)) without building intermediate lists.
In real Python, sorted(b) per bucket (TimSort, in C) beats the pure-Python insertion sort even for small buckets.
sorted(b)is stable TimSort, so replacing the inner sort with it keeps the whole algorithm stable — and it is the idiomatic choice.- The classic formulation for uniform floats in
[0, 1)reduces the index toint(x * n)— no min/max scan needed. - Honest caveat:
sorted(a)on the whole list runs in C at O(n log n) and usually beats this expected-O(n) pure-Python loop; in Python, bucket sort mostly pays off as a bucketing *pattern* (frequency buckets, maximum gap).
- Building buckets with
[[]] * n— every slot is the same list. - Expecting
int()to floor:int(-0.5)is 0 whilemath.floor(-0.5)is -1. Values here are non-negative, but the pattern bites when bucketing signed offsets. - Skipping the
mn == mxguard —ZeroDivisionError.
- Bucket creation: C++
std::vector<std::vector<double>>(n)and Python[[] for _ in range(n)]create independent buckets; in JS/TSnew Array(n).fill([])shares ONE array — useArray.from({ length: n }, () => []). - Inner sort: Python
sorted(b)is stable TimSort in C (the idiomatic choice); JSb.sort()needs the(x, y) => x - ycomparator or numbers sort lexicographically; C++std::sortis unstable introsort — usestd::stable_sortor insertion sort if stability matters. - Truncation: Python
int()and C++static_casttruncate toward zero,Math.floorin JS/TS floors — identical for the non-negative offsets used here, different for signed keys. - Numbers: JS/TS have only doubles, so floats are the natural input; C++ picks the element type explicitly; Python floats are doubles but each list element is a boxed object.
Complexity
Average assumes uniformly distributed keys and k ≈ n buckets. Worst case: all keys in one bucket with an O(n²) inner sort. Stable if the inner sort is stable.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Uniformly distributed floats in a known range (e.g. random numbers in
[0, 1), normalized scores). - When a cheap monotone hash spreads keys evenly and you want expected linear time.
- Problems that only need elements grouped by coarse range (maximum gap, frequency buckets).
- Skewed, clustered, or adversarial data — buckets become unbalanced and the sort degrades to
O(n²). - Unknown value range (you must scan for min/max or guess bucket boundaries).
- Memory is limited — buckets need
O(n + k)extra space.
Alternatives
Common mistakes
- Off-by-one mapping the maximum value to bucket
k(out of range) — clamp tok - 1or add an epsilon to the divisor. - Using Quick Sort inside buckets; it is overkill and unstable — insertion sort is the right inner sort.
- Choosing too few buckets (
k ≪ n), which makes the inner sorts dominate. - Assuming linear time without checking the distribution.
Interview patterns
- Maximum gap:
n - 1buckets of width(max - min)/(n - 1)guarantee the answer is between buckets, not within one. - Top-k frequent elements: bucket elements by frequency (index = count) then read from the high end.
- Sort characters by frequency via frequency buckets.
- Contains duplicate III: buckets of width
t + 1to find values withintinO(n).
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Hash map or array?Beginner
- Average case versus worst caseIntermediate
- Two SumBeginner
- Longest Substring Without Repeating CharactersIntermediate
- Subarray Sum Equals KIntermediate