✒️ Quill

Binary Search, Carefully

by ada · 2026-05-01 · edited 2026-05-01

Binary Search, Carefully

Binary search looks like a four-line algorithm and behaves like one of the most bug-prone things you'll ever write. The trick is to fix on one invariant and never violate it.

The invariant

We maintain two pointers, lo and hi. Both refer to positions in the sorted array. The invariant is:

The answer, if it exists, lies in the half-open interval [lo, hi).

That single sentence dictates every decision in the loop.

def lower_bound(arr, target):
    lo, hi = 0, len(arr)          # interval is [lo, hi)
    while lo < hi:                # interval is non-empty
        mid = (lo + hi) // 2      # mid in [lo, hi)
        if arr[mid] < target:
            lo = mid + 1          # answer can't be at mid; shrink left side
        else:
            hi = mid              # answer might be at mid; shrink right side
    return lo                     # smallest index with arr[i] >= target

lower_bound returns the first index whose value is not less than target. If target isn't in the array, it returns the index where target would be inserted to keep the array sorted.

Why it terminates

Every iteration either does lo = mid + 1 or hi = mid. In both cases, hi - lo strictly decreases, so the loop runs at most ⌈log₂ n⌉ times.

Why people get it wrong

The classic bug is mid = (lo + hi) // 2 overflowing in C/C++ when both are near INT_MAX. In Python you don't care, but the safer pattern is lo + (hi - lo) // 2.

The other classic bug: mixing inclusive [lo, hi] and half-open [lo, hi) interval styles in the same function. Pick one and stick to it. I prefer half-open because hi - lo is the size, the empty interval is lo == hi, and the index hi is naturally "one past the end."

When to reach for it

Anywhere you have a monotone predicate over a sorted (or sortable) range — not just literal arrays. "Smallest x such that we can finish in time t" is a binary search. "Largest k such that the answer fits" is a binary search. The structure is always the same: define the predicate, find the boundary.