Data Structure 📂 Sorting & Order Statistics · 8 of 10 36 min read

Bucket Sort, Hands-On: Scatter, Sort, Gather

An interactive, beginner-friendly Bucket Sort tutorial in Python where the animation and code run in lockstep — values scatter into buckets, each bucket sorts, then everything gathers into a sorted output, with the executing line highlighted at every step. Covers the post-office analogy, the four phases, the index formula, a full worked example, why uniform distribution matters, complexity, benefits, use cases, a comparison with Counting/Radix/comparison sorts, and a general-range variation. Dar

Section 01

The Story That Explains Bucket Sort

The Post Office Sorting Wall
Walk into a mail room and you'll see a wall of pigeonholes — one slot per postcode region. A clerk grabs a fat stack of unsorted letters and, without comparing any two letters to each other, simply glances at each postcode and drops the letter into the matching slot. All the "SW1" letters land together, all the "EC2" letters land together, and so on.

Once every letter is in a slot, each slot holds only a small, similar pile. The clerk does a quick tidy-up within each slot, then walks the wall left-to-right and scoops the slots back into one neat, fully-ordered bundle.

That is Bucket Sort exactly: scatter items into buckets by value, sort each small bucket, then gather them back in order.

Bucket Sort is a distribution sort, not a comparison sort. Instead of repeatedly comparing pairs (like Bubble or Quick Sort), it uses the value itself to decide where an element goes. When data is spread fairly evenly across a range, this can sort in near linear time — faster than the O(n log n) comparison-sort barrier.

🧮
The Core Idea: Scatter → Sort → Gather

Split the range into buckets, drop each value into the bucket that covers its range, sort the (now small) buckets cheaply, then concatenate the buckets in order. The magic is that placing a value into a bucket costs no comparisons at all — just arithmetic.

.12
.42
.91

Each value drops straight into its bucket by value — no comparisons needed.


Section 02

The Four Phases

01
Create Buckets
Make a set of empty buckets (lists). A common choice is one bucket per element, so n values get n buckets.
02
Scatter (Distribute)
For each value, compute a bucket index from the value — for floats in [0, 1) that's int(n × value) — and append it there. No comparisons, just arithmetic.
03
Sort Each Bucket
Each bucket now holds only a few similar values. Sort each one with any simple sort (insertion sort, or Python's built-in .sort()). Small buckets sort fast.
04
Gather (Concatenate)
Walk the buckets left to right and append their contents into one output list. Because buckets cover increasing ranges, the result comes out fully sorted.
📐
The Index Formula

For values uniformly distributed in [0, 1) with n buckets, the bucket for a value x is int(n × x). Example with n = 8: 0.12 → int(8×0.12)=0, 0.42 → int(8×0.42)=3, 0.91 → int(8×0.91)=7. For a general range, first scale: int(n × (x − min) / (max − min)).


Section 03

Hands-On: Watch Each Line Run

The heart of the tutorial. Press Play (or Step) and watch the three phases unfold: values leave the input row and drop into buckets, each bucket gets sorted, then everything is gathered into the output row. At every moment the exact Python line doing that work lights up on the right.

🧮 Live Bucket Sort — code ↔ animation phase: create
1 · Create
2 · Scatter
3 · Sort
4 · Gather
Input array
Buckets  (index = int(n × value))
Output (gathered & sorted)
Press Play or Step ▶ to begin. The highlighted line on the right is the one running.
0Placed
0Buckets sorted
0Gathered
def bucket_sort(arr):
    n = len(arr)
    buckets = [[] for _ in range(n)]
    for x in arr:
        idx = int(n * x)
        buckets[idx].append(x)
    for b in buckets:
        b.sort()
    result = []
    for b in buckets:
        result.extend(b)
    return result
💡
What the colours mean

Amber = the value or bucket being touched right now. Green = sorted / gathered into the final result. Indigo = still waiting. The amber code line is the instruction the interpreter is executing at that instant.


Section 04

The Code, Line by Line — "This Line Does That"

Same program as the animation, now fully annotated. Each line maps to exactly one job the visualiser highlights.

def bucket_sort(arr):
    n = len(arr)                        # how many values (and buckets)
    buckets = [[] for _ in range(n)]    # 1) CREATE n empty buckets
    for x in arr:                       # 2) SCATTER: visit each value
        idx = int(n * x)               #    pick its bucket by value
        buckets[idx].append(x)         #    drop x into that bucket
    for b in buckets:                   # 3) SORT every bucket
        b.sort()                       #    small bucket -> cheap sort
    result = []                          # 4) GATHER into one list
    for b in buckets:                   #    walk buckets left -> right
        result.extend(b)              #    append bucket contents
    return result                       # fully sorted output
🔎 What Each Line Is Responsible For
Line 1
def bucket_sort(arr) — defines the function and receives the list of values in [0, 1).
Line 2
n = len(arr) — counts the values; we'll also use it as the bucket count.
Line 3
buckets = [[] for _ in range(n)]Phase 1: build n empty lists.
Line 4
for x in arrPhase 2 begins: loop over every input value.
Line 5
idx = int(n * x) — compute the destination bucket from the value itself. No comparison.
Line 6
buckets[idx].append(x) — drop the value into its bucket.
Line 7
for b in bucketsPhase 3: iterate over each bucket.
Line 8
b.sort() — sort that bucket in place. Buckets are tiny, so this is cheap.
Line 9
result = []Phase 4: start an empty output list.
Line 10
for b in buckets — walk the buckets in index order (smallest range first).
Line 11
result.extend(b) — append the bucket's sorted contents to the result.
Line 12
return result — hand back the fully sorted list.
OUTPUT — bucket_sort([0.42, 0.37, 0.73, 0.12, 0.91, 0.32, 0.55, 0.78])
[0.12, 0.32, 0.37, 0.42, 0.55, 0.73, 0.78, 0.91]

Section 05

A Full Worked Example

Let's scatter [0.42, 0.37, 0.73, 0.12, 0.91, 0.32, 0.55, 0.78] into n = 8 buckets, using idx = int(8 × value).

Valueint(8 × value)Goes to bucket
0.42int(3.36)3
0.37int(2.96)2
0.73int(5.84)5
0.12int(0.96)0
0.91int(7.28)7
0.32int(2.56)2 (joins 0.37)
0.55int(4.40)4
0.78int(6.24)6
BucketAfter ScatterAfter Sort
0[0.12][0.12]
1[ ] empty[ ]
2[0.37, 0.32][0.32, 0.37]
3[0.42][0.42]
4[0.55][0.55]
5[0.73][0.73]
6[0.78][0.78]
7[0.91][0.91]
🎉
Gather the Result

Walking buckets 0→7 and concatenating gives [0.12, 0.32, 0.37, 0.42, 0.55, 0.73, 0.78, 0.91] — fully sorted. Only bucket 2 ever needed an internal sort, because every other bucket held at most one value.


Section 06

Why Distribution Matters: Uniform vs Skewed

Bucket Sort's speed depends entirely on how evenly values spread across the buckets. Spread them out and it flies; pile them into one bucket and it collapses into whatever sort you used inside.

✅ Uniform — the happy case
BucketItems
01
11
21
...~1 each
ResultO(n + k)
❌ Skewed — the bad case
BucketItems
00
10
2n (all here!)
...0
ResultO(n²)
⚠️
The Catch

If most values land in one bucket, that bucket's inner sort does all the work and you inherit its complexity — typically O(n²) with insertion sort. Bucket Sort assumes (or engineers) a roughly uniform distribution. It is not a general-purpose sorter for arbitrary data.


Section 07

Time & Space Complexity

CaseWhenComplexity
BestValues uniform, ~1 per bucketO(n + k)
AverageReasonably uniform inputO(n + k)
WorstAll values in one bucketO(n²)
SpaceBuckets + outputO(n + k)
Stable?If inner sort is stableYes ✓
In-place?Needs extra bucket storageNo
🔢
Beating the O(n log n) Barrier

Comparison sorts can't do better than O(n log n) in general. Bucket Sort sidesteps that limit by not comparing during distribution — here k is the number of buckets. With k ≈ n and uniform data, the total is effectively O(n). The trade-off is the extra memory for buckets and the uniform-distribution assumption.


Section 08

Benefits

Near-Linear on Uniform Data
O(n + k)
When values spread evenly, it beats O(n log n) comparison sorts because placement uses arithmetic, not comparisons.
🧮
No Comparisons to Place
distribution sort
The expensive scatter step is pure index math. Comparisons only happen inside each small bucket.
⚖️
Stable (If You Want)
preserves order
Use a stable inner sort (like insertion sort) and append in order, and equal keys keep their original relative order.
🔁
Naturally Parallel
independent buckets
Buckets are independent, so sorting them can be farmed out across threads or machines — a big win for huge datasets.
🎯
Tunable
choose k & inner sort
You control the bucket count and the inner sort, letting you tailor it to your data's shape and size.
📊
Great for Floats in a Range
[0, 1) etc.
Tailor-made for normalised floating-point data or scores spread across a known interval.

Section 09

When To Use It — and When Not To

Uniformly Distributed Data
Values spread evenly across a known range — normalised floats, sensor readings, random reals — are the ideal input.
floats in [0, 1)
Known, Bounded Range
When you know min and max up front you can size buckets perfectly. Exam scores 0–100, percentages, ages.
scores, percentages
Parallel / Large Data
Independent buckets can be sorted concurrently, making it attractive for very large, evenly-spread datasets.
multiprocessing, big data
Skewed / Clustered Data
If values clump into a few buckets, it degrades to O(n²). Unknown or lopsided distributions are a poor fit.
clustered values
Tight Memory Budgets
It is not in-place — buckets plus output need O(n + k) extra space. On memory-tight hardware prefer an in-place sort.
embedded, O(1) space needed
General-Purpose Sorting
For arbitrary, unknown data just call sorted() — Python's Timsort is robust and fast without assumptions.
unknown distribution

Section 10

Bucket Sort vs Other Sorts

AlgorithmTypeAverageWorstSpaceStable
Bucket SortDistributionO(n + k)O(n²)O(n + k)Yes*
Counting SortDistributionO(n + k)O(n + k)O(k)Yes
Radix SortDistributionO(d(n + k))O(d(n + k))O(n + k)Yes
Merge SortComparisonO(n log n)O(n log n)O(n)Yes
Quick SortComparisonO(n log n)O(n²)O(log n)No

*Bucket Sort is stable only when the inner bucket sort is stable. k = number of buckets; d = number of digits.

🧩
The Distribution-Sort Family

Bucket, Counting, and Radix Sort are cousins — all avoid comparisons by using values as addresses. Counting Sort suits small integer ranges, Radix Sort sorts digit by digit, and Bucket Sort generalises to real-valued ranges. Radix Sort often uses Counting Sort as its per-digit step, just as Bucket Sort uses a small sort per bucket.


Section 11

Handy Variation — Any Numeric Range

The basic version assumes values in [0, 1). To sort any range, scale each value into that interval first.

def bucket_sort_range(arr, k=10):
    if not arr:
        return arr
    lo, hi = min(arr), max(arr)
    if lo == hi:
        return arr[:]            # all equal -> nothing to do
    buckets = [[] for _ in range(k)]
    span = (hi - lo) / k
    for x in arr:
        idx = int((x - lo) / span)
        if idx == k:               # the max value lands one past the end
            idx -= 1
        buckets[idx].append(x)
    out = []
    for b in buckets:
        out.extend(sorted(b))    # sort + gather in one step
    return out
OUTPUT — bucket_sort_range([29, 25, 3, 49, 9, 37, 21, 43])
[3, 9, 21, 25, 29, 37, 43, 49]

Section 12

Golden Rules

🧮 Bucket Sort — Things to Remember
1
Bucket Sort only wins on uniformly distributed data. If you can't assume that, reach for a comparison sort instead.
2
The index formula is everything: int(n × x) for [0, 1), or int(k × (x − min)/(max − min)) for a general range. Always guard the max value so it doesn't land in an out-of-range bucket.
3
Choose your inner sort deliberately. Insertion sort is great for tiny buckets; Python's .sort() (Timsort) is a safe default.
4
It is not in-place. Budget O(n + k) extra memory for the buckets and the output list.
5
Want stability? Use a stable inner sort and append buckets in order. Then equal keys keep their original sequence.
6
Remember the three words: scatter, sort, gather. If you can picture letters going into pigeonholes and being scooped back out, you understand the whole algorithm.