Invariant
If the target exists, it always remains inside the inclusive [low, high] range.
ALGORITHM LAB / EASY
Repeatedly cut a sorted search space in half. Follow every pointer update and comparison.
01let low = 0, high = values.length - 1;02while (low <= high) {03 const mid = Math.floor((low + high) / 2);04 if (values[mid] === target) return mid;05 if (values[mid] < target) {06 low = mid + 1;07 } else {08 high = mid - 1;09 }10}11return -1;
If the target exists, it always remains inside the inclusive [low, high] range.
Each comparison removes half of the remaining candidates, producing at most log base 2 of n decisions.
Using binary search on unsorted input breaks the elimination rule, even when pointer code looks correct.
Binary search finds a target in a sorted collection by repeatedly comparing it with the middle element and discarding the half that cannot contain it. Every comparison removes about half of the remaining candidates, so a million sorted items need at most 20 comparisons.
The core idea is bigger than searching arrays. Binary search works on any search space where a yes/no question is monotonic: once the answer becomes true, it stays true. That is why the same technique solves problems like finding the minimum ship capacity, the first bad software version, or the square root of a number.
If someone answers only higher or lower, the best first guess is 50. Each answer throws away half of the remaining numbers, so you never need more than 7 guesses. Binary search is exactly this strategy applied to indexes of a sorted array.
Recognize it when: The array is sorted and you must find whether, or where, a value exists.
Recognize it when: first or last position, first element >= x, count occurrences, first true.
Recognize it when: minimize the maximum, maximize the minimum, minimum speed / capacity / days, and a feasible(x) check that flips once.
Recognize it when: rotated, mountain, bitonic, or peak; one side of mid is still ordered.
Recognize it when: square root, precision, continuous answer.
B-tree and B+tree indexes in PostgreSQL and MySQL binary search the sorted keys inside each page to find the next child, so a lookup across millions of rows touches only a few pages.
Finds the commit that introduced a bug by testing the middle commit and discarding half of the history each time. 1,000 commits need about 10 builds.
Java Arrays.binarySearch and Collections.binarySearch, C++ std::lower_bound, and Python bisect are all binary search. Java's TreeMap floorKey and ceilingKey answer the same boundary questions.
GeoIP and firewall rule tables store sorted IP ranges; a binary search finds the range that contains an address.
Load tests binary search the request rate to find the highest throughput a service handles within its latency target.
Set low = 0 and high = n - 1. The whole array is the search space.
Compute mid = low + floor((high - low) / 2). This form avoids integer overflow in languages with fixed-size integers.
If values[mid] equals the target, return mid. The search is complete.
If values[mid] < target, the target can only be to the right, so set low = mid + 1.
If values[mid] > target, the target can only be to the left, so set high = mid - 1.
Loop while low <= high. When low passes high, no candidates remain and the target is absent; return -1. At that moment low is the index where the target would be inserted.
STEP 1The whole array [0, 9] is the search space. If 73 exists, it is between low and high.
values = [4, 11, 18, 29, 37, 51, 64, 73, 82, 96], target = 73
| Step | low | high | mid | values[mid] | Decision |
|---|---|---|---|---|---|
| 1 | 0 | 9 | 4 | 37 | 37 < 73, so low = 5 |
| 2 | 5 | 9 | 7 | 73 | 73 = 73, return 7 |
NOWStep: 1 | low: 0 | high: 9 | mid: 4 | values[mid]: 37 | Decision: 37 < 73, so low = 5
Only 2 comparisons were needed instead of 8 for a left-to-right scan. Searching for 72 continues: low = 5, high = 6, mid = 5 (51 < 72, low = 6), mid = 6 (64 < 72, low = 7). Now low > high, so the result is -1 and 7 is the insertion position.
function binarySearch(values: number[], target: number): number { let low = 0; let high = values.length - 1; while (low <= high) { const mid = low + Math.floor((high - low) / 2); if (values[mid] === target) return mid; if (values[mid] < target) { low = mid + 1; // target is in the right half } else { high = mid - 1; // target is in the left half } } return -1; // low is the insertion point}The range halves every iteration: n, n/2, n/4 ... 1 takes about log2(n) steps.
The first middle element is the target.
Only low, high, and mid are stored.
Each halving adds one stack frame.
Sorting first costs O(n log n). For a single lookup, a linear scan at O(n) is cheaper; binary search wins when you search the same data many times.
On a linked list, reaching the middle costs O(n), which removes the benefit. Use arrays, or balanced trees for dynamic data.
Keeping an array sorted makes each insert O(n). If data changes often, a balanced BST, skip list, or B-tree provides O(log n) search and update.
A hash map gives average O(1) exact lookups, but it cannot answer ordered questions such as nearest value, range queries, or lower bound.
With duplicates, keep searching after a match: move high = mid - 1 to find the first, or low = mid + 1 to find the last, while recording the matched index.
Lower bound finds the first value >= target; upper bound finds the first value > target. Their difference counts occurrences in O(log n).
When the answer is a number in [lo, hi] and feasible(x) is monotonic, binary search x itself. Examples: minimum eating speed, minimum ship capacity, and split array largest sum.
At least one half around mid is always sorted. Check whether the target lies inside the sorted half, then discard the other half.
For continuous answers such as square roots, loop a fixed number of times (for example 100) or until high - low < epsilon instead of comparing integers.
Fix: Use low + (high - low) / 2 so large indexes cannot overflow.
Fix: When high = low + 1, mid equals low forever. Use low = mid + 1, or round mid up when low = mid is required.
Fix: Choose one. Inclusive uses high = n - 1, low <= high, high = mid - 1. Half-open uses high = n, low < high, high = mid.
Fix: The discard rule relies on ordering; verify or sort the input first.
Fix: Continue narrowing after a match and remember the best index found so far.
Each comparison halves the remaining range. After k steps the range size is n / 2^k, which reaches 1 when k = log2(n).
Iterative. It has the same time complexity, uses O(1) space instead of O(log n) stack, and avoids recursion limits.
Compute upperBound(target) - lowerBound(target). Both are O(log n), so the count is O(log n) regardless of how many duplicates exist.
Turn the question into a feasibility check. If feasible(x) is monotonic, binary search the smallest x where it becomes true. Total cost is O(log(range) * cost of feasible).
Technically yes, but locating mid takes O(n), so the total is O(n). A skip list or balanced tree is the proper ordered structure for linked data.
| Problem | Difficulty | What it trains |
|---|---|---|
| Binary Search | Easy | Write the classic inclusive loop from memory. |
| Search Insert Position | Easy | Return low after the loop: the lower bound. |
| First Bad Version | Easy | Binary search over a monotonic predicate. |
| Find First and Last Position of Element | Medium | Lower and upper bound on duplicates. |
| Search in Rotated Sorted Array | Medium | Identify the sorted half each step. |
| Koko Eating Bananas | Medium | Binary search on the answer. |
| Capacity To Ship Packages Within D Days | Medium | Feasibility function plus binary search. |
| Median of Two Sorted Arrays | Hard | Binary search on a partition index. |