Binary search visualizer
Binary search finds a value in a sorted array by checking the middle and throwing away the half that cannot contain it. Step through the classic search and its two cousins, lower bound and upper bound, on an array with repeated values.
still possible middle, being compared equal to the target answer
Press Step or Play. The search starts with the whole array.
- –None yet.
- Values
- 20
- Most comparisons needed
- 5
- Same names elsewhere
- Classic binary search
How to use the simulator
Pick a search and a target, then press Step or Play. Each step compares the target with the middle of the cells that are still possible and greys out the half that cannot hold the answer. The log on the right lists every comparison. New sorted array draws another array, with some values repeated.
- Pick a target that appears more than once and run all three searches. Find stops at whichever copy it happens to hit first; lower bound always ends on the first copy and upper bound just after the last.
- Pick a target that is not in the array. Find reports that it is missing; lower and upper bound both point to where it would be inserted to keep the array sorted.
- Try a target larger than every value. The bounds return the position past the end, shown as an extra cell.
The searches are tested against a plain left-to-right scan on thousands of random arrays, for every target, and never take more than the worst-case number of comparisons.
What is binary search?
Binary search is an algorithm for finding a value in a sorted list. It compares the target with the middle element; if the middle is smaller, the target can only be in the upper half, and if it is larger, only in the lower half. Each comparison discards half of what is left, so a list of a million items takes at most 20 comparisons and a billion at most 30.
It is the method people use with a dictionary or a phone book, opening near where the word should be and narrowing down, made exact. It needs random access to the middle of the list, which is why it works on arrays but not on linked lists, and it needs the list to be sorted, which is why databases and file systems keep their indexes in sorted structures such as B-trees.
How it works
The search keeps two indexes, lo and hi, marking the part of the array where the target could still be. The classic version keeps this invariant: if the target is in the array at all, it is somewhere between lo and hi.
- Start with lo = 0 and hi = n − 1, the whole array.
- While lo ≤ hi, take the middle index mid = lo + (hi − lo) / 2, rounded down.
- If the value at mid is the target, return mid. If it is smaller, set lo = mid + 1; if larger, set hi = mid − 1.
- If lo passes hi, the window is empty and the target is not present; lo is where it would go.
Every step either finds the target or removes mid and everything on one side of it, so the loop always ends. Most bugs in binary search come from breaking the invariant at the edges: setting hi = mid instead of mid − 1 in a closed interval, or looping on lo < hi when the interval is closed.
Lower bound and upper bound
The classic search answers "is it here?" and returns whichever copy it happens to find. Often the question is different: where does the first copy start, or where would a new value go? Lower bound returns the first position whose value is at least the target, and upper bound the first position whose value is greater than it. Both use a half-open window from lo up to, but not including, hi, starting with hi = n so that "past the end" is a possible answer.
Instead of stopping at a match, they keep going until the window is a single position. When the middle value could be the answer, mid stays in the window (hi = mid); otherwise it is dropped (lo = mid + 1). The two differ only in whether equal values count as "too small". Together they answer several common questions:
- Number of copies of x: upper bound − lower bound.
- Where to insert x to keep the array sorted: lower bound (before equal values) or upper bound (after them, which keeps insertion order stable).
- All values between x and y: from lower bound of x up to upper bound of y.
- The nearest value to x: one of the two items next to its lower bound.
How many comparisons
After k comparisons at most n / 2k items remain, so the search needs at most ⌊log₂ n⌋ + 1 comparisons. Growing the array a thousandfold adds only about ten:
| Items | Most comparisons | Linear search, worst case |
|---|---|---|
| 10 | 4 | 10 |
| 1,000 | 10 | 1,000 |
| 1,000,000 | 20 | 1,000,000 |
| 1,000,000,000 | 30 | 1,000,000,000 |
| 1,000,000,000,000 | 40 | 1,000,000,000,000 |
No comparison-based search can do better in the worst case: with k yes/no answers you can tell apart at most 2k positions. In practice, on large arrays, the cost is dominated by memory access rather than comparisons, because each step jumps to a far-away part of the array that is not in the CPU cache. That is one reason B-trees, which make each step look at a whole block of keys, are used for data on disk and for large in-memory indexes.
Off-by-one errors and overflow
Binary search is short but easy to get wrong. The usual mistakes are mixing closed and half-open intervals, which either skips the last candidate or loops forever when the window has two items, and returning the wrong index when the target is missing.
A subtler bug lasted for decades. Computing the middle as (lo + hi) / 2 overflows when the indexes are large: with 32-bit signed integers, lo = 1,200,000,000 and hi = 1,300,000,000 add up to more than 2,147,483,647, the sum wraps around, and (lo + hi) / 2 gives -897,483,648 instead of 1,250,000,000. In 2006 Joshua Bloch reported that this line had been in Java's Arrays.binarySearch for about nine years, and that the binary search Jon Bentley proved correct in Programming Pearls had the same flaw; it only shows up on arrays of around a billion elements. The fix is to compute lo + (hi − lo) / 2, or in Java (lo + hi) >>> 1. The simulator uses the first form.
Searching for an answer
Binary search does not need an array, only a yes/no question whose answer flips once: no, no, no, then yes from some point on. Lower bound is exactly "find the first yes". That pattern solves problems such as:
- The smallest truck capacity that can ship all packages within a number of days: capacity x either works or not, and if x works, so does anything larger.
- The integer square root of n: the largest x with x² ≤ n.
- The first bad commit in a history, which is what
git bisectdoes, asking you to test the middle commit each time.
The search runs over the range of possible answers instead of array indexes, and needs about log₂ of the range's size checks.
In standard libraries
| Language | Find | Lower and upper bound |
|---|---|---|
| C++ | std::binary_search (true or false) | std::lower_bound, std::upper_bound, std::equal_range |
| Python | None built in | bisect.bisect_left, bisect.bisect_right |
| Java | Arrays.binarySearch, which returns −(insertion point) − 1 when absent | None; use the insertion point |
| Go | slices.BinarySearch | sort.Search: first index where a condition is true |
| Rust | slice::binary_search, returning Err(insertion point) when absent | slice::partition_point |
Note that the "find" functions that return a position make no promise about which copy they return when there are duplicates; use lower bound when it matters.
History
According to Donald Knuth, binary search was first mentioned in print by John Mauchly in 1946, in the Moore School Lectures on electronic computers. Early versions only worked for array sizes one less than a power of two; Knuth credits D. H. Lehmer with the first version that worked for every n, in 1960. Despite being one of the first algorithms students learn, it has stayed a byword for subtle bugs, from off-by-one errors to the overflow Bloch described in 2006.
Binary search questions and answers
What is binary search?
Binary search finds a value in a sorted array by comparing it with the middle element. If they differ, the value can only be in the half on one side, so the search continues there, halving the remaining range each time. It needs at most floor(log₂ n) + 1 comparisons: 20 for a million items, 30 for a billion.
What is the time complexity of binary search?
O(log n) comparisons in the worst and average case, and O(1) extra memory for the iterative version. The array must already be sorted; sorting it first costs O(n log n), which pays off only if you search it many times.
What is the difference between lower bound and upper bound?
Lower bound is the first position whose value is greater than or equal to the target; upper bound is the first position whose value is strictly greater. On an array with duplicates, lower bound points at the first copy of the target and upper bound just past the last, so their difference is the number of copies. If the target is absent, both point at where it would be inserted.
Why does (lo + hi) / 2 overflow?
With 32-bit signed integers, lo + hi exceeds 2,147,483,647 once both indexes are past about a billion, and the sum wraps around to a negative number. For lo = 1,200,000,000 and hi = 1,300,000,000, (lo + hi) / 2 gives -897,483,648. Computing lo + (hi − lo) / 2 avoids it. Joshua Bloch reported in 2006 that this bug had been in Java’s Arrays.binarySearch for about nine years.
Can binary search be used on unsorted data?
Not directly. It relies on order: when the middle value is smaller than the target, everything to its left must be smaller too. More generally, binary search works on any monotone yes/no question, such as “is capacity x enough?”, which is how it is used to search for an answer rather than in an array.
Is binary search faster than a hash table?
A hash table lookup is O(1) on average, binary search O(log n), so for exact lookups a hash table is usually faster. Binary search on a sorted array uses less memory and also answers range and nearest-value questions (the first item at least x, everything between x and y), which a hash table cannot.
Further reading
- Joshua Bloch, Google Research blog, 2006Nearly All Binary Searches and Mergesorts are BrokenThe midpoint overflow bug and how it was found.
- Python documentationbisect: Array bisection algorithmLower and upper bound in Python, with recipes for searching sorted lists.
- cppreferencestd::lower_boundThe C++ definition, complexity guarantee and example.
- Git documentationgit bisectBinary search over commits to find the one that introduced a bug.