Coding Interview Patterns Module 7 – Sorting as a tool
Counting, bucket and radix sort
Sort without comparing: counting sort for small ranges, buckets that find the widest gap in linear time, and LSD radix sort for fixed-width keys.
What you will learn
- Sort small-range integers in O(n + k) time with a stable counting sort
- Find the widest gap between sorted neighbours in O(n) time with buckets
- Prove with the pigeonhole principle that the widest gap runs between buckets
- Sort fixed-width keys with LSD radix sort and choose its base
- Explain why a comparison sort needs about n log₂ n comparisons
Before you start
What you’ll learn · Before you start
What you will learn
- Sort small-range integers in O(n + k) time with a stable counting sort
- Find the widest gap between sorted neighbours in O(n) time with buckets
- Prove with the pigeonhole principle that the widest gap runs between buckets
- Sort fixed-width keys with LSD radix sort and choose its base
- Explain why a comparison sort needs about n log₂ n comparisons
Before you start
On this page
Counting sort, bucket sort and radix sort put values in order without ever comparing two of them, which lets them beat the n log n cost of comparison sorts when the values are small integers or keys of fixed width. Counting sort counts how often each value occurs and rebuilds the list from the counts, in O(n + k) time for values from 0 to k. Bucket sort spreads values over ranges of equal width and deals with each range on its own. Radix sort orders fixed-width keys digit by digit, with one stable counting pass per digit, last digit first. This lesson’s problem needs the sorted order of a large log only at its widest gap, and asks for it in linear time.
The problem: the longest quiet spell
The problem
A parcel hub’s scanner records the second at which each parcel passes, times[0..n − 1], in the order the belts
report them, which is not time order. The shift manager wants the longest quiet spell: the largest difference
between two neighbouring times once the times are in increasing order. Return 0 for fewer than two scans. Limits:
0 ≤ n ≤ 10⁶ and 0 ≤ times[i] ≤ 10⁹, and the solution must run in O(n) time.
[3600, 120, 900, 7200, 1500]gives 3600. In order the times are 120, 900, 1500, 3600 and 7200, and the last two are furthest apart.[500, 500, 500]gives 0: three parcels in the same second.[86399, 0]gives 86399.
The first example in time order
Text description of the diagram
A horizontal time line, in seconds after the start of the shift, carries five dots, one per scan of the first example, at 120, 900, 1500, 3600 and 7200.
A bracket above each pair of neighbours gives the gap between them: 780 from 120 to 900, 600 from 900 to 1500, 2100 from 1500 to 3600, and 3600 from 3600 to 7200. The last bracket is drawn thicker and labelled as the longest, so the answer for this log is 3600 seconds.
The brute force, and why sorting is not allowed
For every scan, look through all the scans for the next later time, and keep the largest difference. That is exactly n² steps: 4,000,000 for 2,000 scans and 10¹² for a million. Sorting first and comparing neighbours is fast in practice, but it breaks the stated limit, and the run shows why no comparison sort can meet it:
# The longest quiet spell by brute force: for each scan, look through every scan for the next later time.
# Then the cost of the usual fix, sorting first, and the least any comparison sort can do.
import math
import random
def longest_gap_brute(times):
"""Returns the largest gap between consecutive times in sorted order, and the steps taken: exactly n * n."""
steps = 0
best = 0
for t in times:
following = None # the smallest time later than t
for u in times:
steps += 1
if u > t and (following is None or u < following):
following = u
if following is not None:
best = max(best, following - t)
return best, steps
for times in ([3600, 120, 900, 7200, 1500], [500, 500, 500], [86399, 0]):
print(times, "->", longest_gap_brute(times)[0])
rng = random.Random(3)
for n in (1_000, 2_000):
_, steps = longest_gap_brute([rng.randint(0, 10**9) for _ in range(n)])
print(f"n = {n:,}: {steps:,} steps")
print(f"n = 1,000,000: {10**12:,} steps")
class Counted:
"""A time that counts how often sorted() compares it with another (sorted() only uses <)."""
comparisons = 0
def __init__(self, value):
self.value = value
def __lt__(self, other):
Counted.comparisons += 1
return self.value < other.value
sorted(Counted(rng.randint(0, 10**9)) for _ in range(100_000))
print(f"sorting 100,000 times: {Counted.comparisons:,} comparisons")
bound = math.ceil(math.lgamma(1_000_001) / math.log(2))
print(f"any comparison sort, 1,000,000 distinct times: at least {bound:,} comparisons on its worst input") Output
[3600, 120, 900, 7200, 1500] -> 3600 [500, 500, 500] -> 0 [86399, 0] -> 86399 n = 1,000: 1,000,000 steps n = 2,000: 4,000,000 steps n = 1,000,000: 1,000,000,000,000 steps sorting 100,000 times: 1,532,014 comparisons any comparison sort, 1,000,000 distinct times: at least 18,488,885 comparisons on its worst input
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 gap_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Python’s sort compared 1,532,014 pairs for 100,000 times, close to n log₂ n. That is no quirk of one library. A sort that only compares must tell apart all n! orders of n different values, and each comparison has two outcomes, so some input needs at least log₂ n! comparisons: 18,488,885 for a million times. Linear time means not comparing the times with each other at all.
What to notice
The times are integers in a known range, so a time can serve as an address, the way counting sort uses each value. And the widest gap is never narrower than the average gap: the n − 1 gaps between neighbours add up to the largest time minus the smallest, so at least one of them is that wide. Two times that are closer together than the average gap can never be the two ends of the answer.
Buckets narrower than the average gap Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why the widest gap always runs between buckets: the pigeonhole proof
- A dry run on the first example
- The code in Python, JavaScript, Java, C++ and Go
- Three slips, caught by a stress test
- Counting sort, measured on a million exam marks
- LSD radix sort for fixed-width keys
- Choosing among the three
- Edge cases and common bugs
- Variants, with answers
- Follow-up questions, answered
- Exercise and practice set
- Find the widest gap in sorted order, without sorting
- Check yourself
- Interview questions
- When is counting sort a poor choice, even though it runs in O(n + k)?
- A service must sort 10⁸ passenger ages, each from 0 to 120, held in a byte array, with only 20 MB of extra memory and a two-second budget. A teammate proposes the language's built-in sort. What do you propose, and what does it cost in time and memory?
- You found the widest gap between sorted neighbours in O(n) with buckets. The interviewer asks why the answer can never lie inside a bucket, why each bucket keeps only its minimum and maximum, and what changes for real numbers.
- Sort 10⁷ ten-digit mobile numbers held as 64-bit integers, as fast as you can. Built-in sort, counting sort or radix sort? If radix sort, which base, how many passes, and why must each pass be stable?
- Key takeaways
1 diagram · 5 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 25 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Dictionary of Algorithms and Data Structures: counting sort (National Institute of Standards and Technology)
- Dictionary of Algorithms and Data Structures: bucket sort (National Institute of Standards and Technology)
- Dictionary of Algorithms and Data Structures: radix sort (National Institute of Standards and Technology)
- Dictionary of Algorithms and Data Structures: stable (National Institute of Standards and Technology)
- Sorting Techniques (Python Software Foundation)
- Postal addressing systems: India (Universal Postal Union)
- std::ranges::minmax_element (cppreference.com)
- Go standard library: package slices (Min, Max) (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress