Bucket sort
Bucket sort distributes values into buckets by range, sorts each small bucket, and concatenates the buckets. When the input is spread evenly it runs in linear expected time.
Learning goals
- Map a value to its bucket with arithmetic
- Sort each bucket and concatenate them
- Explain how the distribution of the input decides the running time
Prerequisites
To sort a pile of letters by name, put them into labelled trays A-C, D-F and so on, then sort each small tray by hand. Each tray holds only a few letters, so the work stays small as long as the letters spread over the trays.
Distribution assumptions
Bucket sort assumes the input values are drawn roughly uniformly from a known range, such as real numbers in [0, 1) or integers in [0, 100). Under that assumption each of n buckets receives about one element.
If the assumption fails and most values land in a few buckets, the buckets become the bottleneck and the algorithm is only as fast as the algorithm used inside the buckets.
Bucket construction
For n values in [0, 100) use n buckets and the index x * n / 100. For doubles in [0, 1) use (int) (x * n). The index is computed directly from the value, with no comparisons between elements.
Append each element to its bucket in input order. Use a linked list or ArrayList for each bucket. The animation labels each bucket with the range of values it covers.
1static void bucketSort(double[] a) { // values in [0, 1)2int n = a.length;3List<List<Double>> buckets = new ArrayList<>();4for (int i = 0; i < n; i++) buckets.add(new ArrayList<>());5for (double x : a) buckets.get((int) (x * n)).add(x);6for (List<Double> b : buckets) Collections.sort(b);7int k = 0;8for (List<Double> b : buckets)9for (double x : b) a[k++] = x;10}
Bucket sort
Interactive · edit the inputint[] arr · length 10
bucket 0 [0-9]
bucket 1 [10-19]
bucket 2 [20-29]
bucket 3 [30-39]
bucket 4 [40-49]
bucket 5 [50-59]
bucket 6 [60-69]
bucket 7 [70-79]
bucket 8 [80-89]
bucket 9 [90-99]
10 buckets, each covering a slice of [0, 100). Bucket index = x · n / 100, so a value's bucket is decided by arithmetic, not by comparisons.
1public static void bucketSort(int[] arr) { // values in [0, 100)2int n = arr.length;3List<List<Integer>> buckets = new ArrayList<>();4for (int i = 0; i < n; i++) buckets.add(new ArrayList<>());5for (int x : arr) buckets.get(x * n / 100).add(x);6for (List<Integer> b : buckets) Collections.sort(b);7int k = 0;8for (List<Integer> b : buckets)9for (int x : b) arr[k++] = x;10}
Live state
n
10
Execution trace (dry run table)Show 24 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=10 | 10 buckets, each covering a slice of [0, 100). Bucket index = x · n / 100, so a value's bucket is decided by arithmetic, not by comparisons. |
| 2 | L5 | x=78 bucket=7 | 78 · 10 / 100 = 7. Append it to bucket 7. |
| 3 | L5 | x=17 bucket=1 | 17 · 10 / 100 = 1. Append it to bucket 1. |
| 4 | L5 | x=39 bucket=3 | 39 · 10 / 100 = 3. Append it to bucket 3. |
| 5 | L5 | x=26 bucket=2 | 26 · 10 / 100 = 2. Append it to bucket 2. |
| 6 | L5 | x=72 bucket=7 | 72 · 10 / 100 = 7. Append it to bucket 7. |
| 7 | L5 | x=94 bucket=9 | 94 · 10 / 100 = 9. Append it to bucket 9. |
| 8 | L5 | x=21 bucket=2 | 21 · 10 / 100 = 2. Append it to bucket 2. |
| 9 | L5 | x=12 bucket=1 | 12 · 10 / 100 = 1. Append it to bucket 1. |
| 10 | L5 | x=23 bucket=2 | 23 · 10 / 100 = 2. Append it to bucket 2. |
| 11 | L5 | x=68 bucket=6 | 68 · 10 / 100 = 6. Append it to bucket 6. |
| 12 | L6 | bucket=1 size=2 | Sort bucket 1 (17, 12) with insertion sort: 1 comparison. Small buckets are cheap to sort. |
| 13 | L6 | bucket=2 size=3 | Sort bucket 2 (26, 21, 23) with insertion sort: 3 comparisons. Small buckets are cheap to sort. |
| 14 | L6 | bucket=3 size=1 | Bucket 3 holds one value, nothing to sort. |
| 15 | L6 | bucket=6 size=1 | Bucket 6 holds one value, nothing to sort. |
| 16 | L6 | bucket=7 size=2 | Sort bucket 7 (78, 72) with insertion sort: 1 comparison. Small buckets are cheap to sort. |
| 17 | L6 | bucket=9 size=1 | Bucket 9 holds one value, nothing to sort. |
| 18 | L9 | bucket=1 k=2 | Copy bucket 1 back into arr[0..1]. |
| 19 | L9 | bucket=2 k=5 | Copy bucket 2 back into arr[2..4]. |
| 20 | L9 | bucket=3 k=6 | Copy bucket 3 back into arr[5..5]. |
| 21 | L9 | bucket=6 k=7 | Copy bucket 6 back into arr[6..6]. |
| 22 | L9 | bucket=7 k=9 | Copy bucket 7 back into arr[7..8]. |
| 23 | L9 | bucket=9 k=10 | Copy bucket 9 back into arr[9..9]. |
| 24 | L10 | Sorted. The fullest bucket holds 3 values. If values spread evenly each bucket stays tiny and the total is O(n); if they all land in one bucket it degrades to the cost of the inner sort. |
In-bucket sort
Each bucket is sorted separately. Insertion sort is the traditional choice, because buckets are small and insertion sort has almost no overhead on a handful of elements. Collections.sort also works, and it is stable.
Stability of the whole algorithm follows from two things: elements enter each bucket in input order, and the sort inside the bucket is stable.
Expected complexity
Distributing costs O(n), concatenating costs O(n + number of buckets), and sorting the buckets costs the sum of cost(bucket size). For uniform data the expected bucket size is constant, so the expected total is O(n + k) with k buckets.
In the worst case every element falls into one bucket, which costs the inner sort on n elements: O(n²) with insertion sort, or O(n log n) with a merge-based sort. Compare the “Even spacing (ideal)” preset with “Clustered (worst case)” and read the size of the fullest bucket.
| Input shape | Largest bucket | Total time (insertion sort inside) |
|---|---|---|
| Uniform spread | about 1-3 elements | O(n) |
| Moderately skewed | a few elements | between O(n) and O(n²) |
| All in one bucket | n elements | O(n²) |
Best
O(n + k)
Average
O(n + k)
Worst
O(n²)
Space
O(n + k)
Practice ladder
Now try it yourself
Easy
- Sort an array of doubles in [0, 1) with bucket sort
- Sort test scores 0-100 using 10 buckets
Medium
- Top K frequent elements with buckets indexed by frequency
- Sort strings by their first letter into 26 buckets, then within each bucket
Advanced
- Choose bucket boundaries from sample quantiles for non-uniform data
- Maximum gap between consecutive sorted values in linear time using buckets
Key takeaways
- Bucket sort maps each value to a bucket by arithmetic, sorts each bucket, then concatenates.
- Expected time is O(n + k) for uniformly distributed input; the worst case is the inner sort on all n elements.
- It is stable when buckets are filled in input order and sorted with a stable sort.