The idea
Imagine finding the word “quantum” in a paper dictionary. You don’t start at page 1. You open it near the middle, see you’re at “M”, and immediately know the word is in the second half. Then you split that half, and so on.
Binary search does exactly this on a sorted array:
- Look at the middle element.
- If it is the target — done.
- If the target is bigger, throw away the left half. If it is smaller, throw away the right half.
- Repeat on the half that is left.
Each step halves the number of candidates. That’s why it is so fast.
Step by step
We keep two indexes, low and high, marking the part of the array that could still contain the target:
low = 0, high = n - 1
while low <= high:
mid = (low + high) // 2
if a[mid] == target: return mid
if a[mid] < target: low = mid + 1 # go right
else: high = mid - 1 # go left
return -1 # not found
In the 3D model, ruled-out elements sink and fade, so you can see the remaining range shrink: 15 → 7 → 3 → 1.
Why O(log n)?
After k steps there are about n / 2ᵏ candidates left. We stop when that reaches 1, so 2ᵏ = n, which means k = log₂ n.
| n (sorted items) | Linear search (worst) | Binary search (worst) |
|---|---|---|
| 15 | 15 | 4 |
| 1,000 | 1,000 | 10 |
| 1,000,000 | 1,000,000 | 20 |
| 1,000,000,000 | 1,000,000,000 | 30 |
Doubling the data adds just one more step.
Code
def binary_search(a, target):
low, high = 0, len(a) - 1
while low <= high:
mid = (low + high) // 2
if a[mid] == target:
return mid
elif a[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
a = [3, 9, 14, 21, 28, 35, 42, 50, 57, 63, 71, 78, 84, 90, 97]
print(binary_search(a, 63)) # 9
print(binary_search(a, 40)) # -1
#include <iostream>
#include <vector>
using namespace std;
int binarySearch(const vector<int>& a, int target) {
int low = 0, high = (int)a.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // avoids integer overflow
if (a[mid] == target) return mid;
if (a[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
int main() {
vector<int> a = {3, 9, 14, 21, 28, 35, 42, 50, 57, 63, 71, 78, 84, 90, 97};
cout << binarySearch(a, 63) << "\n"; // 9
}
Notice mid = low + (high - low) / 2 in C++. With very large arrays, low + high can overflow a 32-bit int; this form cannot.
Libraries have it built in: Python’s bisect module and C++’s std::lower_bound / std::binary_search.
Binary search is a way of thinking
The same “halve the search space” idea solves many problems that don’t look like searching at all:
- Find the first or last position of a value (lower / upper bound).
- Find the square root of a number to a given precision.
- “Binary search on the answer”: the minimum speed to finish a task in time, the largest possible minimum distance, and so on.
git bisectfinds the commit that introduced a bug by halving the history.
Common mistakes
- Using it on an unsorted array — the answer will be wrong, silently.
- Writing
while low < highinstead of<=and missing the last element. - Updating
low = midorhigh = midinstead ofmid ± 1, which can loop forever.
Complexity at a glance
| Case / operation | Time | Why |
|---|---|---|
| Best case | O(1) | The target is exactly in the middle. |
| Average / worst case | O(log n) | The range halves every step. |
| Linear search (for comparison) | O(n) | Checks elements one by one. |
| Extra space | O(1) |
Quick check
Test yourself — pick an answer to see if you got it.
1. What must be true about the array before you can use binary search?
Binary search decides which half to discard by comparing with the middle — that only works if the array is sorted.
2. low = 4 and high = 10. What is mid?
mid = (4 + 10) / 2 = 7.
3. About how many comparisons does binary search need for 1,000,000 sorted items?
log₂(1,000,000) ≈ 20, because 2²⁰ ≈ 1 million.
4. When does the loop stop if the target is not present?
When low passes high, the search range is empty — there is nowhere left to look.