Algorithms
Interpolation Search: Guessing Where the Answer Is
Binary search always stabs the exact middle. But if you're hunting for "Wagner" in a phone book, you don't crack it open at the halfway point — you flip most of the way to the back, because you know W is near the end. Interpolation search encodes exactly that intuition: instead of probing index (lo+hi)/2, it estimates where the key should fall based on its value, assuming the data is roughly uniformly distributed. On a uniform array of a billion sorted integers, it finds a target in about log₂(log₂ n) ≈ 5 probes — versus binary search's ≈ 30.
The payoff is a jaw-dropping average of O(log log n), one of the few natural algorithms sub-logarithmic in comparisons. The catch: feed it adversarial or skewed data and it degrades all the way to O(n), worse than the O(log n) it was trying to beat. It's a gamble — a good one when you know your distribution.
- Avg time (uniform)O(log log n)
- Worst timeO(n)
- SpaceO(1) iterative
- RequiresSorted + numeric keys
- Best forLarge uniform datasets
- Invariantkey ∈ A[lo..hi] if present
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
The core idea: let the value pick the index
Binary search treats every sorted array identically — it only ever asks "is the key less than the middle element?" It ignores how much less. Interpolation search, first described by W. W. Peterson in 1957 (IBM Journal of R&D) and analyzed rigorously by Perl, Itai, and Avni (1978) and independently by Yao and Yao (1976), exploits the magnitudes.
The governing assumption is that the sorted array A[lo..hi] behaves like samples from a uniform distribution. Then the fraction of the value range that the target key occupies should equal, approximately, the fraction of the index range where it lives. That gives the interpolation probe formula — a linear interpolation between the two endpoints:
pos = lo + (key − A[lo]) × (hi − lo) / (A[hi] − A[lo])Read it geometrically: draw a straight line from the point (lo, A[lo]) to (hi, A[hi]); ask where that line hits height key; that x-coordinate is your guess. If keys really are uniform, the line is an excellent predictor and pos lands within a whisker of the answer. The invariant is identical to binary search's: if key is present, it lies in A[lo..hi] — we only ever shrink that window, and the value-based probe is just a smarter choice of where to cut it.
The algorithm, step by step
The loop keeps a half-open-ish window [lo, hi] and narrows it each iteration. The steps:
- Guard the window: continue only while
lo ≤ hiandA[lo] ≤ key ≤ A[hi]. That second clause is critical — it catches out-of-range keys immediately and, more subtly, keeps the denominator meaningful. - Compute the probe with the interpolation formula, clamped to
[lo, hi]. - Compare
A[pos]tokey: on equality, returnpos; ifA[pos] < key, setlo = pos + 1; elsehi = pos − 1. - Handle the flat segment: when
A[lo] == A[hi]the denominator is 0 — treat that block specially (returnloif it equals key, else fail).
int interp(int[] A, int key) {
int lo = 0, hi = A.length - 1;
while (lo <= hi && key >= A[lo] && key <= A[hi]) {
if (A[lo] == A[hi]) // flat block
return A[lo] == key ? lo : -1;
long pos = lo + (long)(key - A[lo]) * (hi - lo)
/ (A[hi] - A[lo]);
if (A[pos] == key) return (int)pos;
if (A[pos] < key) lo = (int)pos + 1;
else hi = (int)pos - 1;
}
return -1;
}Note the long cast: (key − A[lo]) × (hi − lo) can overflow 32-bit integers even when the array is modest, because it multiplies two large quantities before dividing. This is the single most common bug in interpolation-search implementations.
Why it's O(log log n) — the derivation
The intuition-defying speedup comes from how fast the window shrinks. In binary search, each probe halves the number of candidate indices: n → n/2 → n/4, giving log₂ n steps. In interpolation search on uniform data, each probe shrinks the window not by a constant factor but by a square root.
Here's the heart of it. Model the elements in [lo, hi] as m = hi − lo + 1 order statistics of a uniform distribution. The interpolation probe is an unbiased estimate of the target's rank; the standard deviation of a rank in a uniform sample of size m is on the order of √m. So after one probe the surviving window contains roughly √m elements, not m/2. Iterating:
n → √n → n^(1/4) → n^(1/8) → … → 2The window size is n raised to (1/2)ᵏ after k probes. We reach a constant size when (1/2)ᵏ·log n ≈ 1, i.e. when 2ᵏ ≈ log₂ n, i.e. k ≈ log₂ log₂ n. That is the celebrated Θ(log log n) expected number of probes, proven by Yao & Yao (1976) as tight for the uniform model. To feel the magnitude: for n = 2³² ≈ 4 billion, log₂ n = 32 and log₂ log₂ n = 5. Five probes to search four billion records.
The space complexity is O(1) for the iterative form — just the two indices — same as binary search. Each probe does more arithmetic (a multiplication and a division vs. binary search's single shift), so the constant factor per step is higher; the win comes purely from needing dramatically fewer steps.
The O(n) worst case, and how it happens
The log-log bound is an average over uniform inputs. The worst case is O(n) — no better than a linear scan and strictly worse than binary search's guaranteed O(log n). This is the price of gambling on the distribution.
The pathology is exponentially skewed data. Suppose A = [1, 2, 4, 8, 16, …, 2ⁿ] and you search for a small key like 3. The interpolation formula computes the fraction (3 − 1)/(2ⁿ − 1), which is essentially 0, so pos lands right next to lo every single time. The window shrinks by just one element per probe: n → n−1 → n−2 → …. That's Θ(n) probes, each with an expensive division. On such data, interpolation search is the worst of both worlds — linear and arithmetic-heavy.
- Clustered keys (e.g., timestamps bunched around business hours, or IDs with large gaps) produce the same degradation, just less extreme.
- Duplicate-heavy arrays flatten segments, forcing the
A[lo] == A[hi]fallback and stalling progress. - Because the failure mode is data-dependent, it's a genuine denial-of-service surface: an attacker who controls inserted keys can force worst-case behavior on demand — a reason production code often refuses to use it on untrusted input.
Hybrids and variants that tame the worst case
Because the raw algorithm is fragile, practical designs bound the downside:
- Interpolation-Binary Search (Santoro & Sidney, 1985): interpolate, but if the window fails to shrink by at least a factor over a couple of probes, fall back to a binary-search midpoint. This caps the worst case at O(log n) while keeping the O(log log n) average on nice data — arguably the version you'd actually ship.
- Guarded / clamped interpolation: force
posto move at least one position toward the middle, and clamp it inside(lo, hi), preventing the one-step-per-probe stall. - Three-point / quadratic interpolation: use a curved fit through three samples to model non-uniform (but smooth) distributions more accurately — more arithmetic per probe, better guesses on mildly skewed data.
- Interpolation search on strings: map key prefixes to numeric fractions (a form of radix interpolation) to search sorted string tables; used in some dictionary and DNA-index structures.
A close cousin worth naming: exponential search (galloping) probes indices 1, 2, 4, 8, … to bracket the key, then binary-searches the bracket. It shares interpolation search's "jump far" spirit but makes no distributional assumption and keeps an O(log n) worst case — often the safer default for unbounded or skewed data.
Where it actually earns its keep
Interpolation search shines in a specific regime: large, immutable, in-memory sorted arrays of numeric keys drawn from a known, near-uniform distribution, where you do many lookups and probe count dominates.
- Database and storage indexing: pages of a B+ tree, or entries in an SSTable/LSM block, often hold keys that are approximately uniform (e.g., hashed keys, monotonically issued IDs). Interpolation within a leaf can cut comparisons versus binary search on wide fan-out nodes.
- Learned indexes: Kraska et al.'s 2018 "The Case for Learned Index Structures" (Google) generalizes exactly this idea — replace the interpolation line with a trained model that predicts a key's position, then do a bounded local search around the prediction. Interpolation search is the linear-model special case.
- Time-series and telemetry: looking up a timestamp in a densely, regularly sampled log is close to uniform, so the value-based guess is nearly exact.
- Static lookup tables in embedded / numeric code (e.g., inverting a monotone calibration curve), where the mapping is smooth and predictable.
Conversely, for general-purpose sorted-array search over unknown data, the standard library sticks with binary search (std::lower_bound, Arrays.binarySearch, bisect) precisely because its O(log n) bound is guaranteed and its per-probe cost is a single cheap shift. Interpolation search is a scalpel, not a default.
Pitfalls, edge cases, and the honest per-probe cost
Even on well-behaved data, several traps recur:
- Integer overflow: as noted,
(key − A[lo]) × (hi − lo)multiplies before dividing and overflows fast. Use a wider type or reorder as a floating-point ratio. - Division by zero: whenever
A[hi] == A[lo](flat window), the denominator is 0. You must special-case it — a missing guard is a crash, not a slowdown. - Off-by-one and the range guard: dropping the
key ≥ A[lo] && key ≤ A[hi]check letsposcompute outside[lo, hi]and index out of bounds. The guard both terminates early on absent keys and keeps the probe valid. - Cache behavior: like binary search, probes jump non-locally, so both are cache-hostile compared to a linear scan of a small block. On modern CPUs, a linear or SIMD scan beats any logarithmic search for n up to ~64–128 elements — which is why real B-tree nodes and hybrid searches switch to a linear/branchless sweep at the bottom.
- The division is expensive: integer division is 20–40× slower than a shift on many pipelines and doesn't vectorize. So interpolation search only wins when its probe-count advantage (log log n vs log n) outweighs a heavier per-probe cost — which requires n to be genuinely large and the distribution genuinely uniform.
The takeaway: interpolation search is a beautiful, distribution-aware algorithm with an astonishing average bound, but it trades binary search's iron-clad guarantee for a bet on your data. Know the distribution, guard the fallback, and it can be a real win; skip it on adversarial or skewed input.
| Property | Interpolation Search | Binary Search | Linear Scan |
|---|---|---|---|
| Avg comparisons (uniform) | O(log log n) | O(log n) | O(n) |
| Worst-case comparisons | O(n) | O(log n) | O(n) |
| Probe location | value-proportional guess | arithmetic midpoint | next index |
| Cost per probe | ≈ 1 mult, 1 div | 1 shift/add | 1 compare |
| Data assumption | roughly uniform, numeric | sorted only | none |
| Space | O(1) | O(1) | O(1) |
Frequently asked questions
Why not just always use binary search?
Binary search guarantees O(log n) regardless of the data — safe but distribution-blind. Interpolation search averages O(log log n) on uniform data, roughly a 6× reduction in probes at n = 4 billion (5 vs 32). The trade is that interpolation can degrade to O(n) on skewed data and pays a costlier division per probe, so it only wins when the data is large and near-uniform.
What exactly is the complexity?
On uniformly distributed keys the expected number of probes is Θ(log log n), proven tight by Yao & Yao (1976). The worst case is O(n) (e.g., exponentially spaced keys). Space is O(1) for the iterative form. Note the per-probe cost is higher than binary search's — a multiplication and a division versus a single shift.
When does interpolation search break down?
When the data is far from uniform — exponentially spaced or heavily clustered keys make each probe advance by only one position, giving Θ(n) behavior. Duplicate-heavy arrays flatten the interpolation denominator to zero. Because the failure is input-dependent, it's also a DoS vector on attacker-controlled keys, so avoid it on untrusted input or use a binary-fallback hybrid.
What's the interpolation formula, and why that shape?
pos = lo + (key − A[lo]) × (hi − lo) / (A[hi] − A[lo]). It's a straight-line (linear) interpolation between the endpoints (lo, A[lo]) and (hi, A[hi]), asking where that line reaches height 'key'. On uniform data the line is an excellent position predictor, landing within about √(window) of the true rank.
How is it used in real systems?
It appears inside database index blocks (SSTables, B+ tree leaves) where keys are near-uniform, in time-series lookups over regularly sampled timestamps, and in numeric lookup-table inversion. Most influentially, Kraska et al.'s 2018 learned-index work generalizes it — replacing the interpolation line with a trained model that predicts a key's position, then doing a bounded local search around the guess.
Is there a version that keeps the speed but avoids the O(n) blowup?
Yes — Interpolation-Binary Search (Santoro & Sidney, 1985) interpolates but falls back to a binary midpoint if the window isn't shrinking fast enough, capping the worst case at O(log n) while preserving the O(log log n) average on friendly data. Guarded/clamped variants that force at least one-step progress toward the middle achieve similar robustness.