Exponential Search
Double an index until the target is bracketed, then binary search inside that bracket.
Overview
Exponential search finds a target in a sorted array by probing indices 1, 2, 4, 8, … until it finds a[i] ≥ target (or runs off the end), then running Binary Search on [i/2, i]. The cost is O(log i) where i is the target's position — independent of `n`.
That makes it the right tool for unbounded or infinite sorted sequences (a stream, a sorted array whose length you cannot query) and for arrays where the target is likely near the front. TimSort uses the same "galloping" idea to merge runs faster when one run's elements are consistently smaller.
Intuition
A mental model before the formal terms.
Searching for a house number on an endless street with no map: check house 1, then 2, 4, 8, 16… As soon as you pass the number you want, you know it lies between the last two houses you checked, and that stretch is at most as long as the distance you already walked. Then binary search the stretch.
How it works
- If
a[0] == target, return0. - Set
i = 1. Whilei < nanda[i] < target, doublei. - The target, if present, lies in
[i/2, min(i, n - 1)]. - Binary search that range and return the result.
Why it works
Sortedness guarantees that once a[i] ≥ target, no index beyond i can hold a smaller value, and every index below i/2 was already certified < target.
The doubling phase takes ⌈log₂ i⌉ probes and the bracket has length ≤ i/2, so the binary search phase takes another O(log i) — total O(log i).
Recognition
How to tell a problem wants this.
- A sorted sequence of unknown or infinite length ("the array is infinite", "read-only API returning elements by index").
- The target is expected to sit close to the start, so
log iis much smaller thanlog n. - Merging two sorted sequences of very different sizes, where you want
O(m log(n/m))instead ofO(m + n).
Interactive visualization
Play, step, change the input. ← → and space work too.
1if a[0] == target: return 02bound = 13while bound < n and a[bound] < target: bound *= 24lo = bound // 2, hi = min(bound, n - 1)5binary search target in a[lo..hi]Pseudocode
1if a[0] == target: return 02i = 13while i < n and a[i] < target: i *= 24return binarySearch(a, target, lo = i / 2, hi = min(i, n - 1))Implementations
1from bisect import bisect_left2from typing import Callable, Optional, Sequence3 4 51 · Double the bound until it overshoots the target6def exponential_search(a: Sequence[int], target: int) -> int:7 n = len(a)8 if n == 0:9 return -110 if a[0] == target:11 return 012 13 bound = 114 while bound < n and a[bound] < target:15 bound *= 216 172 · Binary search the bracket (bound/2, min(bound, n-1)] found above18 lo, hi = bound // 2, min(bound, n - 1)19 while lo <= hi:20 mid = (lo + hi) // 221 if a[mid] == target:22 return mid23 if a[mid] < target:24 lo = mid + 125 else:26 hi = mid - 127 return -128 29 303 · The point of it: cost depends on the answer position, not the size31def probes_for(position: int) -> int:32 doublings = 033 b = 134 while b < position:35 b *= 236 doublings += 137 return 2 * doublings # ~log2(pos) doublings + ~log2(pos) binary steps38 39 404 · bisect can search the located bracket directly via lo/hi arguments41def exponential_search_bisect(a: Sequence[int], target: int) -> int:42 n = len(a)43 bound = 144 while bound < n and a[bound] < target:45 bound *= 246 i = bisect_left(a, target, bound // 2, min(bound + 1, n))47 return i if i < n and a[i] == target else -1bound //= 2never appears: the bracket isbound // 2tomin(bound, n - 1), computed once after the doubling loop settles.bisect_left(a, target, lo, hi)takes explicit bounds, so the second phase is a single standard-library call over exactly the bracket found.- The
hiargument tobisect_leftis exclusive, which is why it ismin(bound + 1, n)rather thanmin(bound, n - 1). - The hand-written version is kept alongside so the mechanism is visible;
exponential_search_bisectis what production code should call. a[0] == targetshort-circuits index 0, which the bracket[bound // 2, ...]would otherwise exclude.
With bisect_left doing phase two in C, the Python-level loop is only the doubling phase — about log2(i) iterations.
bisect_left(a, x, lo, hi)has takenlo/hisince forever, which makes bracketed binary search a one-liner; thekey=parameter arrived in 3.10.- Python integers never overflow, so
bound *= 2is safe to any magnitude — the loop is bounded by the sequence length, not by a word size. - For a lazily paged source, wrap the accessor in
functools.lru_cacheso repeated probes at the same index cost one fetch. Sequence[int]rather thanlist[int]lets the function accept tuples, ranges, and custom sequence types.
- Passing
min(bound, n - 1)as thehiargument tobisect_left, which is exclusive and therefore excludes the last candidate index. - Dropping the index-0 check and returning -1 for a target that sits at the front.
- Calling
bisect_lefton an unsorted sequence, which returns a plausible index with no error at all.
- Out-of-range reads: JS/TS return
undefined, which makes the unbounded accessor form natural; C++ is undefined behaviour and Python raisesIndexError, so both need an explicit length or a guarded accessor. - Phase two is a library call in C++ (
std::lower_boundover an iterator sub-range) and Python (bisect_leftwithlo/hi), and hand-written in JS/TS. - Overflow of the doubling bound is a real concern only in C++ with a signed
int; JavaScript doubles and Python big integers both stay exact well past any container size. - The async variant (paged API, one network probe per step) is idiomatic only in JS/TS, where the accessor can return a
Promise.
Complexity
i is the index of the target; at most 2·log₂ i + O(1) comparisons.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Sorted data of unknown or unbounded length.
- Targets biased towards the beginning of the array.
- Galloping merges of unequal-length sorted runs (as in TimSort) and set intersections of very different sizes.
- Bounded arrays with uniformly random targets — plain Binary Search is simpler with the same asymptotics.
- Unsorted data.
- Sequences without random access; doubling needs
O(1)index access.
Alternatives
Common mistakes
- Using
hi = iwithout clamping ton - 1after overshooting the array. - Starting the binary search at
lo = 0instead ofi/2, throwing away theO(log i)guarantee. - For an "infinite" array API, forgetting to treat out-of-range reads as
+∞.
Interview patterns
- Search in a sorted array of unknown size: gallop with a read that returns a sentinel past the end.
- Find the first index
kwitha[k] ≥ targetin a stream that supports random reads. - Explain how TimSort's galloping mode uses exponential search to skip long already-ordered stretches during merge.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Where does O(n log n) come from?Beginner
- Questions to ask before binary searchingIntermediate
- Minimum Size Subarray SumIntermediate
- Search in Rotated Sorted ArrayIntermediate