Binary Search: Explained Visually
Binary search is a way to find an item in a sorted list by checking the middle element and throwing away the half where the item cannot be. Each check cuts the search space in half, so even a list of 1 billion items needs only about 30 checks. It is one of the most important algorithms for coding interviews, and the same idea powers database indexes, git bisect and many optimisation problems.
- The idea behind binary search, and when you can use it
- How low, mid and high move, step by step
- Why it takes O(log n) time
- Iterative and recursive code in Python, Java and C++
- Lower bound, first/last occurrence and binary search on the answer
- The bugs that fail most interview solutions
Contents
The idea: the dictionary trick
When you look up the word "neuron" in a printed dictionary, you do not start at page 1. You open it near the middle, see "lemon", and know neuron must be after it. You throw away the first half and repeat with the second half. In a few flips you are on the right page.
Binary search does exactly this with an array. It only works when the data is sorted (or, more generally, when you can always tell which half the answer is in).
Binary search, step by step
We keep two pointers, low and high, around the part of the array where the target could still be. At each step:
- Find the middle: mid = (low + high) / 2 (rounded down).
- If arr[mid] == target, we found it.
- If arr[mid] < target, the target must be on the right, so set low = mid + 1.
- If arr[mid] > target, the target must be on the left, so set high = mid − 1.
- If low > high, the range is empty, so the target is not in the array.
Try it below. Pick a value that is present, then one that is missing, and step through.
Why binary search is so fast: O(log n)
Every check halves the remaining items: n → n/2 → n/4 → … → 1. The number of halvings needed to get from n down to 1 is log2(n). Drag the slider to feel the difference:
| Array size n | Linear search (worst case) | Binary search (worst case) |
|---|---|---|
| 100 | 100 checks | 7 checks |
| 1,000 | 1,000 | 10 |
| 1,000,000 | 1,000,000 | 20 |
| 1,000,000,000 | 1,000,000,000 | 30 |
| Complexity | Value |
|---|---|
| Best case | O(1): the first mid is the target |
| Average and worst case | O(log n) |
| Space (iterative) | O(1) |
| Space (recursive) | O(log n), for the call stack |
Sorting costs O(n log n). If you only search once, a simple O(n) linear scan is cheaper than sorting first. Binary search pays off when you search the same sorted data many times, or when the data is already sorted.
Binary search code
Iterative binary search in Python
def binary_search(arr, target):
low, high = 0, len(arr) - 1
steps = 0
while low <= high:
mid = (low + high) // 2
steps += 1
if arr[mid] == target:
return mid, steps
elif arr[mid] < target:
low = mid + 1 # target is in the right half
else:
high = mid - 1 # target is in the left half
return -1, steps
arr = [3, 8, 12, 17, 21, 26, 31, 38, 44, 52, 57, 63, 70, 81, 95]
print(binary_search(arr, 57))
print(binary_search(arr, 40))
(10, 4) (-1, 4)
57 is found at index 10 in 4 checks. 40 is not in the array, and binary search proves that in 4 checks too, while a linear search would need all 15.
The same code in other languages
static int binarySearch(int[] arr, int target) {
int low = 0, high = arr.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // avoids integer overflow
if (arr[mid] == target) return mid;
if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
int binarySearch(const vector<int>& arr, int target) {
int low = 0, high = (int)arr.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // avoids integer overflow
if (arr[mid] == target) return mid;
if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
// The STL has it built in: std::binary_search, std::lower_bound, std::upper_bound
def binary_search_rec(arr, target, low, high):
if low > high:
return -1 # empty range: not found
mid = (low + high) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
return binary_search_rec(arr, target, mid + 1, high)
return binary_search_rec(arr, target, low, mid - 1)
In Java and C++, low + high can be larger than the maximum int (about 2.1 billion) for big arrays, which overflows to a negative number. low + (high - low) / 2 gives the same middle without overflow. Python integers never overflow, so (low + high) // 2 is fine there. Interviewers love this question.
Variations you must know
Plain binary search answers "is it there?". Most interview problems need a variation.
Lower bound: first position where value ≥ target
This version never stops early. It keeps shrinking until low == high, which is the first index whose value is greater than or equal to the target. With it you can find the first occurrence, the last occurrence, the count of a value, and where to insert a new value.
def lower_bound(arr, target):
"""First index whose value is >= target (where target would be inserted)."""
low, high = 0, len(arr) # note: high = len(arr), and low < high
while low < high:
mid = (low + high) // 2
if arr[mid] < target:
low = mid + 1
else:
high = mid
return low
marks = [35, 42, 42, 42, 58, 67, 91]
first = lower_bound(marks, 42)
last = lower_bound(marks, 43) - 1
print("first 42 at", first, "| last 42 at", last, "| count =", last - first + 1)
print("insert 60 at index", lower_bound(marks, 60))
import bisect
print(bisect.bisect_left(marks, 42), bisect.bisect_right(marks, 42))
first 42 at 1 | last 42 at 3 | count = 3 insert 60 at index 5 1 4
Python's built-in bisect_left is exactly lower bound, and bisect_right gives the position just after the last equal value.
Binary search on the answer
This is the most powerful idea. Sometimes you are not searching an array at all. You are searching for the smallest (or largest) value that works, and you can check any candidate quickly. If "works" is monotonic (once a value works, every bigger value also works), you can binary search over the possible answers.
Problem (Koko eating bananas): there are piles of bananas, and Koko has h hours. Each hour she eats up to k bananas from one pile. What is the smallest speed k that lets her finish in time?
import math
def min_speed(piles, hours):
"""Smallest eating speed k so that all piles finish within `hours`."""
def can_finish(k):
return sum(math.ceil(p / k) for p in piles) <= hours
low, high = 1, max(piles)
while low < high:
mid = (low + high) // 2
if can_finish(mid):
high = mid # mid works, try slower
else:
low = mid + 1 # too slow, go faster
return low
print(min_speed([3, 6, 7, 11], 8))
print(min_speed([30, 11, 23, 4, 20], 5))
4 30
The same pattern solves "minimum capacity to ship packages in D days", "split array largest sum", "square root of a number" and "aggressive cows".
Common bugs (and how to avoid them)
| Bug | What happens | Fix |
|---|---|---|
while low < high with high = mid - 1 |
Skips checking the last element | Use low <= high with mid ± 1, or low < high with high = mid |
low = mid instead of mid + 1 |
Infinite loop when two elements remain | When the mid is ruled out, always move past it |
low + high overflow |
Wrong mid in Java/C++ for huge arrays | low + (high - low) / 2 |
| Unsorted input | Wrong answers, silently | Sort first, or make sure the condition is monotonic |
| Off-by-one in the return | Returns the insert position instead of -1 | Decide clearly: exact match or lower bound |
Pick one template and use it always. Exact match: low=0, high=n-1, while low <= high, move mid±1. Boundary search (first true): low=0, high=n, while low < high, high = mid when the condition is true, otherwise low = mid + 1, return low.
Where binary search is used in real life
- Database indexes (B-trees) use the same halving idea to find a row among millions.
- git bisect finds the commit that introduced a bug by testing the middle commit each time.
- Autocomplete finds the range of words starting with a prefix in a sorted list.
- Machine learning: searching for a good threshold or learning rate when the metric changes in one direction.
- Operating systems: finding free memory blocks and scheduling.
- Binary search finds a target in a sorted array by repeatedly checking the middle and discarding half.
- Time O(log n): 1 billion items need only about 30 checks. Iterative space is O(1).
- Use
mid = low + (high - low) / 2in Java and C++ to avoid overflow. - Lower bound finds the first value ≥ target; with it you can find first and last occurrences and insert positions.
- Binary search on the answer works whenever "does value x work?" is monotonic.
Test yourself
Score: 0 / 51. What must be true about the data for binary search to work?
2. Roughly how many checks does binary search need for 1,000,000 items in the worst case?
3. Why use low + (high - low) / 2 instead of (low + high) / 2 in Java?
4. arr = [2, 4, 4, 4, 9]. What does lower_bound(arr, 4) return?
5. "Find the minimum speed to finish in h hours" is solved with binary search over what?
Frequently asked questions
Is binary search faster than a hash table lookup?
No. A hash table lookup is O(1) on average, and binary search is O(log n). But binary search needs no extra memory, keeps the data in order, and can answer range questions ("all values between 40 and 60", "next bigger value") that a hash table cannot.
Can binary search work on a linked list?
Technically yes, but it is pointless. Finding the middle of a linked list takes O(n) time, so the total becomes O(n), no better than a linear scan. Binary search needs random access, such as an array.
What is the difference between bisect_left and bisect_right in Python?
For a value that appears several times, bisect_left returns the index of the first occurrence (lower bound) and bisect_right returns the index just after the last occurrence (upper bound). Their difference is the count of that value.
Iterative or recursive binary search, which is better?
Iterative is usually better: it uses O(1) extra space and has no recursion overhead. Recursive code can look cleaner and is fine for interviews, but uses O(log n) stack space.
What is ternary search?
Ternary search splits the range into three parts and is used to find the maximum or minimum of a function that increases and then decreases (unimodal). For searching a sorted array, binary search is faster than ternary search.
Real interview walkthroughs and AI explained simply on our YouTube channel.