How it works
The explanation, with examples you can run.
Search for a boundary
The most useful form doesn't ask "is the target here?" but "where is the first element that is ≥ target?" That one function, usually called lower bound (bisect_left in Python, std::lower_bound in C++), answers several questions at once: whether the target is present, where its first copy is, and where it would be inserted to keep the array sorted.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
The invariant is: everything before lo is less than the target, and everything from hi onwards is greater than or equal to it. The answer is always in [lo, hi]. When arr[mid] < target, mid belongs to the "less than" part, so lo = mid + 1. Otherwise mid might be the answer, so hi = mid keeps it. Each iteration shrinks the range by at least one, so the loop ends, and when lo === hi that is the boundary.
The usual bugs
- Mixed conventions. Using a closed range
[lo, hi](hi = length - 1,while (lo <= hi)) is fine, and so is a half-open one. Mixing their update rules skips elements or loops forever when two elements are left. - Returning any match. A "return as soon as
arr[mid] === target" search finds a match. With duplicates you usually want the first or last. - Unsorted input. Binary search on data that isn't sorted by the same comparison returns nonsense without any error. Sort with the same comparator you search with.
- Overflow. In Java, C and C++,
(lo + hi) / 2overflows whenlo + hiexceeds the largest integer. Joshua Bloch wrote in 2006 that the JDK's ownbinarySearchhad this bug, and it only appeared for arrays of about 2³⁰ elements. JavaScript numbers are doubles, soMath.floor((lo + hi) / 2)is safe for any array length, but the popular shortcut(lo + hi) >> 1converts to a 32-bit integer and goes negative:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Test it against something obviously right
Binary search is easy to check: compare it with a linear scan on thousands of small random inputs, including empty arrays, duplicates and targets outside the range.
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
Beyond arrays
The same halving works on anything monotonic: the first version where a test fails (git bisect), the smallest buffer size that is fast enough, or the square root of a number to a given precision. If you can answer "is the answer at or below x?" with yes or no, and the answers flip from no to yes exactly once, you can binary search it.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.
Go deeper
- Nearly all binary searches and mergesorts are broken (Joshua Bloch, Google Research blog) ↗research.google
- bisect: array bisection algorithm (Python docs) ↗docs.python.org
- Binary search (Sedgewick and Wayne, Algorithms 4th ed., section 3.1) ↗algs4.cs.princeton.edu
- Binary search (Wikipedia) ↗en.wikipedia.org