Binary Search Invariants: A Formal Treatment
Binary search is often taught as a pattern to memorize. This paper gives a formal treatment using loop invariants, so you can derive correct implementations and avoid off-by-one errors.
The Problem
Given a sorted array and a value , find index such that , or determine that is not present.
The Invariant
We maintain indices and such that:
- Invariant: If is in , then .
- Bounds: (or when the range is empty).
Initially, and , so the invariant holds: the full array contains if it exists.
The Loop
Each iteration computes and compares with :
- If , we return .
- If , then cannot be in (array is sorted). Set .
- If , then cannot be in . Set .
In each case, we shrink the range while preserving the invariant. When , the range is empty and is not in the array.
Termination
The quantity (the size of the search range) decreases by at least 1 each iteration. It starts non-negative, so the loop terminates.
Implementation
def binary_search(A: list[int], v: int) -> int:
lo, hi = 0, len(A) - 1
while lo <= hi:
mid = (lo + hi) // 2
if A[mid] == v:
return mid
if A[mid] < v:
lo = mid + 1
else:
hi = mid - 1
return -1
The invariant guides the implementation. No memorization required.