Most binary searches scan a sorted array of data. But some problems have no array to search at all — instead they ask for a single number (a minimum speed, a smallest capacity, a largest gap) where you can check any guess but cannot directly compute the answer. The trick is to binary-search the space of possible answers itself. This is one of the highest-leverage patterns in interviews because once you spot it, a problem that looked like brute force collapses to a tidy O(n log range).
Koko Eating Bananas. Given piles = [3, 6, 7, 11] and h = 8 hours, find the minimum eating
speed k (bananas per hour) so Koko finishes every pile within h hours. At speed k, a pile of
p bananas takes ceil(p / k) hours. We binary-search k over the range [1, max(piles)] and keep
the smallest k that fits the time budget. The answer here is k = 4.
Intuition
Imagine you are buying the slowest (cheapest) machine that still finishes a job by a deadline. Faster machines obviously also make the deadline, and slower machines obviously miss it. So if you line up every machine speed from slow to fast, there is a single tipping point: everything to the right of it works, everything to the left fails.
That ordering is the whole game. You do not have to try every speed — you can bisect. Test a middle speed: if it makes the deadline, the answer is that speed or slower, so throw away the faster half; if it misses, the answer is faster, so throw away the slower half. Each test halves the candidates.
Walk through it
On the right, the cells are the candidate speeds 1 through 11. The lo and hi pointers bracket the live window; mid is the speed we are currently testing. At each mid we run a feasibility check — hours(k) = ceil(3/k) + ceil(6/k) + ceil(7/k) + ceil(11/k) — and compare it to the 8-hour budget, shown in the readout below the strip.
Watch the window shrink. At mid = 6 the job takes 6 hours (within budget), so 6 is feasible and we pull hi down to keep [1, 6]. At mid = 3 it takes 10 hours (over budget), so we push lo up to 4. Then mid = 5 works, and finally mid = 4 works — lo and hi collide at 4, the slowest speed that still finishes in time. Discarded speeds dim out; the final answer locks in green.
The code, line by line
import math
def min_eating_speed(piles, h):
def feasible(k): # hours needed at speed k
hours = sum(math.ceil(p / k) for p in piles)
return hours <= h # finishes within budget?
lo, hi = 1, max(piles) # answer lives in [1, max]
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid): # mid works...
hi = mid # ...maybe slower works too
else: # mid too slow
lo = mid + 1 # need a faster speed
return lo # smallest feasible speedfeasible(k)is the predicate. It does not find the answer — it only answers yes/no for one guess. Keeping it as a tiny helper is the cleanest way to write these problems.lo, hi = 1, max(piles)bounds the answer. Speed0is meaningless, and any speed abovemax(piles)clears every pile in one hour, so it can never be the minimum.hi = mid(notmid - 1): whenmidworks it is still a candidate for the smallest answer, so we keep it in the window.lo = mid + 1: whenmidfails,midand everything slower are hopeless, so we move strictly past it.- The loop runs while
lo < hiand returnslowhen they meet — that converged value is the leftmost (smallest) speed for whichfeasibleis true.
Complexity
| Case | Time | Notes |
|---|---|---|
| Time | O(n log m) (moderate) | log m binary-search steps, each an O(n) feasibility scan |
| Best | O(n) (moderate) | lo and hi already adjacent |
| Space | O(1) (fast) | just a few integer variables |
O(1) (fast)Here n is the number of piles and m = max(piles) is the size of the answer range. We do about log m halvings, and each one runs feasible over all n piles — so O(n log m). Compare that to checking every speed one by one, which is O(n m).
When to use / pitfalls
The tell is a problem that asks for a minimum or maximum value where you cannot compute it
directly but you can write a yes/no check, and that check is monotone (once it flips from no to
yes it stays yes). Capacity-to-ship-in-D-days, split-array-largest-sum, and minimum-time problems are
all the same move: define feasible(x), then binary-search x. Say the words "binary search on the
answer" out loud and the interviewer will know you have seen the pattern.
This only works when the predicate is monotone. Prove it first: here, a slower speed never takes
fewer hours, so feasibility flips exactly once. Also get the boundary update right — use hi = mid
(keep the candidate) when searching for a minimum, and lo = mid + 1 on failure. Writing
hi = mid - 1 would skip the answer.
Practice
At speed k = 3 with piles [3, 6, 7, 11], how many hours does Koko need, and does it beat the 8-hour budget?
1. What property of the feasibility check makes binary search on the answer valid?
2. Why is the upper bound set to max(piles)?
3. On a successful test, why do we write hi = mid instead of hi = mid - 1?
4. What is the time complexity, with n piles and m = max(piles)?