← SciSim / Computer Science

[^] Exponential Search

Unbounded Arrays | Doubling Phase + Binary Search | O(log n)

Standard Undergraduate

Section 1 -- Interactive Simulation

Speed:
0
Operations
0
Comparisons
0/0
Step
16
n
n: 16     
EXPONENTIAL-SEARCH(A, target):
  if A[0] == target: return 0
  i = 1
  while i < n and A[i] <= target:
    i = i * 2 // double bound
  // Binary search in [i/2, min(i, n-1)]
  return BINARY-SEARCH(A, i//2, min(i, n-1), target)

Section 2 -- The Idea, Step by Step

Imagine flipping through a fat dictionary to find a word near the front. You don't turn one page at a time, and you don't open it dead-centre either. You jump a little, then jump twice as far, then twice as far again — until you've clearly gone past the word — and only then do you settle down and search carefully in the short stretch you just leapt over. That two-gear strategy is exponential search: sprint to get into the neighbourhood, then walk to pin down the exact spot.

The list has to be sorted, like page numbers. Say your value sits at position $k$ in an array of size $n$. In the first gear you probe positions $1, 2, 4, 8, 16, \ldots$ — each step doubling the index — and you stop the instant a value overshoots your target (or you run off the end). Because you double, it only takes about $\log_2 k$ jumps to fly past position $k$. The pay-off: you never needed to know how big the array was. That is exactly why exponential search shines on unbounded or unknown-length lists, where plain binary search can't even start because it needs $n$ up front.

Two gears, written out. Doubling phase: set $i = 1$ and keep doing $i \gets 2i$ while $A[i] \le \text{target}$ and $i < n$ — that costs $\lceil \log_2 k \rceil$ probes. Binary-search phase: the target is now trapped in the bracket $[\,i/2,\ \min(i,\,n-1)\,]$, whose width is at most $i/2 \le k$, so cleaning it up costs another $O(\log k)$. Add them: $T = O(\log k)$, with $n$ nowhere in sight. In the worst case the target sits at the very end, $k = n$, and you fall back to $O(\log n)$ — no worse than ordinary binary search.

A quick number makes the win concrete. In a million-element array, binary search always spends about $\log_2(10^6) \approx 20$ probes. If your target is way out at $k \approx 1000$, exponential search isn't faster — it doubles to 1024 in ten steps, then binary-searches a 512-wide window in nine more, roughly the same 20. But if the target is near the front at $k = 8$, exponential search needs only about $3 + 3 = 6$ probes while binary search still grinds through all 20. The closer the target is to the start, the bigger the head start.

Try this with the controls above. First, drag the n slider from small to large and notice that the search cost grows only like $\log n$, not like $n$ — the bars pile up but the work barely moves. Second, switch the preset to Sorted: exponential search is only correct on ordered data, so this is the case the doubling-then-bracket logic in the pseudo-code is built for. Third, read down the pseudo-code panel line by line and trace where $i$ would land for a target near the front versus one near the end — that single difference is the whole reason the algorithm exists.

Section 3 -- Complexity Analysis

SymbolMeaning
nInput size
T(n)Time complexity function
hHeight or depth of structure
kKey or rank parameter
W(n)Worst-case complexity
A(n)Average-case complexity
Step 1 -- Doubling Phase. Start i=1. Double until A[i] > target or i >= n. If target is at position k, this phase takes O(log k) steps.
Step 2 -- Binary Search Phase. Target is in range [i/2, i]. Range size = i/2 <= k. Binary search: O(log(i/2)) = O(log k).
Step 3 -- Total Complexity. T(n,k) = O(log k) + O(log k) = O(log k). In worst case k=n so O(log n). Advantage: O(log k) < O(log n) when target is near beginning.
Step 4 -- Unbounded Arrays. Primary use: array with unknown size. Binary search needs n. Exponential search finds the range without knowing n by checking validity before access.
Step 5 -- Space. O(1) iterative. Recursive binary search phase: O(log k) stack.
Step 6 -- vs Binary Search. Same worst case O(log n). Better when target is near start O(log k). Commonly used in text editors (gap buffers), systems without known array bounds.

Section 4 -- FAQ

Why double instead of add a fixed step? concept+
Doubling reaches the right range in O(log k) steps. Adding fixed step +c takes O(k/c) steps -- still linear in k.
How does it handle infinite arrays? applied+
Access A[1], A[2], A[4], A[8],... checking validity each time. First out-of-bounds or A[i]>target gives upper bound. Works even when n is unknown.
When is it better than Binary Search? concept+
When target is likely near the beginning. For time-series: searching from the end for recent entries. k-th element from start with small k.
What is the actual bound improvement? complex+
If target is at position k: Exponential uses 2*ceil(log2(k)) steps. Binary Search uses ceil(log2(n)) steps. Improvement factor = log(n)/log(k). For k=sqrt(n): 2x speedup. For k=log(n): speedup is huge.
Is it used in any real systems? nonobv+
Yes -- GNU libc uses it in some string functions. Text editors with gap buffers use exponential search for cursor movement. Some database systems use it for B-tree range scans.
What if the array has negative indices or starts at non-zero? deep+
Adjust: let base = lo, i=1. Check A[base+i] instead of A[i]. Binary search on [base+i/2, base+i]. Works for any starting offset.

Section 5 -- Common Misconceptions

Conceptual Misconceptions

X Exponential Search is only for infinite arrays.

O It also improves performance on finite arrays when target is likely near the beginning.

X It requires O(log n) doubling steps in all cases.

O Only O(log k) steps where k is target position. For target near start, far fewer than log n.

X Doubling always gives range [i/2, i] of size exactly i/2.

O Range size is i - i/2 = i/2 (floor). For i=1: range [0,1] size 2. Binary search within this range, not just i/2 elements.

Implementation Errors

X Forgetting to check array bounds before accessing A[i].

O For finite arrays: check i < n. For infinite/unbounded: check A[i] exists. Out-of-bounds access is a common crash.

X Using i = i+1 instead of i = i*2 in doubling phase.

O i = i+1 gives linear O(k) steps. Must double: i = i*2 or i <<= 1 to get O(log k).

X Not checking A[0] first before entering loop.

O A[0] check is the base case. Without it, the loop condition may skip index 0 if the target happens to equal A[0].