Binary search finds the boundary of a monotone predicate in evaluations. Searching a sorted array is the least interesting use. The big ones are binary searching the answer of an optimisation problem, and descending a data structure.

One template, one invariant

// predicate is F F ... F T T ... T on [lo, hi); find the first T (or hi if none)
ll lo = LO, hi = HI;            // invariant: pred false on [LO, lo), true on [hi, HI)
while (lo < hi) {
    ll mid = lo + (hi - lo) / 2;  // floor division for lo >= 0; use (lo + hi) >> 1 with negatives
    if (pred(mid)) hi = mid; else lo = mid + 1;
}
// lo == hi == first true
  • For the last true in a T...T F...F predicate, search the first false and subtract one.
  • Alternative form that some people prefer: keep l always false and r always true, loop while (r - l > 1), and the answer is r. Pick one form and never mix them.
  • hi must be a provable bound. Too small silently clamps the answer; too large can overflow inside pred (for example mid * mid with mid near ).
  • (lo + hi) / 2 rounds toward zero, which breaks the invariant when the range includes negatives. lo + (hi - lo) / 2 or (lo + hi) >> 1 (arithmetic shift is floor) are safe.

Bitwise descent is the same search written over bits. It finds the largest with pred(x) true when the predicate is T..T F..F, and it’s the shape used by Fenwick and binary-lifting descents:

ll x = 0;
for (int b = K - 1; b >= 0; b--) if (pred(x | 1LL << b)) x |= 1LL << b;

STL

CallReturns
lower_bound(all(a), x)first
upper_bound(all(a), x)first
upper_bound - lower_boundcount of
prev(lower_bound(...))last (check begin)
partition_point(all(a), pred)first element where pred is false (pred must be T..T F..F)
ranges::lower_bound(a, x, {}, &T::key)projection onto a field

On set/map use the member s.lower_bound(x). The free function is on non-random-access iterators.

double lo = 0, hi = 1e9;
for (int it = 0; it < 100; it++) {           // fixed count: always terminates
    double mid = (lo + hi) / 2;
    (pred(mid) ? hi : lo) = mid;
}

while (hi - lo > eps) can loop forever: when values are around a double can’t represent a difference of . 100 iterations exhaust double precision for any range. For a relative-precision answer spanning many magnitudes, search on or bisect the bit pattern of the double.

Binary search on the answer

If “can we achieve ?” is monotone in , optimisation becomes feasibility. The statement phrases are “minimise the maximum”, “maximise the minimum”, “minimum time such that”, “largest such that”.

ProblemFeasibility check for
split array into parts minimising the max sumgreedy: cut when the running sum would exceed
place points maximising the minimum gapgreedy from the left
-th smallest pairwise sum / distance / productcount pairs with two pointers, per check
-th smallest in an row/column-sorted matrixstaircase count,
-th smallest of with signscount with two pointers on sorted positive/negative parts
max average subarray with length subtract , then check for a non-negative sum of length using prefix minima
max median of a subarray with length map to by comparing with , same check
minimum time for machines to make items (watch overflow: cap the sum)
minimum largest edge so that the graph is connected / has a perfect matching / 2-SAT holdsrebuild with edges
minimum radius / time with a geometric conditionreal binary search + check
minimum with a DP insidethe DP becomes simpler with a fixed threshold

Combinations worth recognising: binary search + 2-SAT (separation constraints), binary search + max flow (scheduling by a deadline), binary search + matching (bottleneck assignment), binary search + shortest path (minimise the maximum edge, or a parametric cost), binary search + DP (the check re-runs a or cheaper DP with the threshold baked in — often simpler than the unconstrained DP, since fixing removes a dimension).

Fractional programming

To maximise () over a feasible set, binary search and check , which is a linear objective. Dinkelbach’s iteration is faster in practice: where is optimal for . It converges superlinearly, usually in 5-10 rounds.

Uses: minimum mean cycle, optimal ratio spanning tree, maximum density subgraph (each check is a min cut), “best average” knapsack (choose items maximising : sort by ).

When the check costs a log too

Total cost is . If both the outer search and the check are , consider descending a structure instead (below). If there are many independent searches over the same sequence of updates, use parallel binary search.

Searching inside a structure

StructureDescent
Fenwick treefind the smallest prefix with sum in by bitwise descent, instead of
segment treemax_right(l, pred) / min_left: walk down to the first index where the aggregate stops satisfying pred
treap / order-statistic treedescend by subtree sizes for -th or rank
tree ancestorsbinary lifting: jump while the condition holds (first ancestor with value , LCA)
wavelet tree / persistent segment tree-th smallest in a range by descending on counts
convex hull / sorted anglesbinary search the tangent or the sector
int bitLowerBound(ll k) {            // Fenwick t[1..n] (non-negative values), smallest i with prefix(i) >= k
    int pos = 0;
    for (int pw = 1 << __lg(n); pw; pw >>= 1)
        if (pos + pw <= n && t[pos + pw] < k) pos += pw, k -= t[pos];
    return pos + 1;
}

Tested against brute-force linear scan for the smallest prefix , on random non-negative Fenwick arrays.

Other forms

  • Exponential (galloping) search. Double hi until pred(hi) holds, then binary search . It costs , which matters when the answer is small but the range is or unbounded.
  • Integer roots. by binary search with overflow-safe power (stop multiplying once the product exceeds ), or pow followed by correction.
  • Median of two sorted arrays. Binary search the split of the shorter array so that left parts are all right parts: . Generalises to the -th element of two sorted arrays.
  • Interpolation search. on uniformly distributed keys, worst case. Rarely useful in contests.
  • Binary search over a permutation of candidates. If the predicate is monotone in a sorted order of objects (edges by weight, times of events), sort the objects and binary search the index.
  • Interactive binary search. The budget is tight, so every query must halve the candidates exactly (Interactive).

Pitfalls

Verify monotonicity

A non-monotone predicate gives a plausible wrong answer, not a crash. On a small input, brute-force pred(X) for every and check the pattern is F..FT..T.

  • Off-by-one: decide whether you search the first true or the last true, and write it in a comment.
  • Overflow inside the check: sums of , products mid * mid, counts of pairs. Cap them or use __int128.
  • Real searches with an epsilon loop: use a fixed iteration count.
  • The answer may be “impossible”: check pred(HI) before trusting the result.