Where it runs
1. The problem
Prefix sums relied on the data standing in a row, so you could add up any stretch. Sorted data has another useful property: order. When a list is in order, you don't need to look at everything to find one item. One check removes half of the list.
You have a million log lines, stored in order of time. Someone asks for the entry at 14:32:07. You could read from the top, line by line, until you reach it. On a million lines that is up to a million reads for one search, and searches happen all the time.
When the data is already sorted, any item out of a million is found in about 20 checks instead of a million.
2. In plain words
Take the sorted test list [1, 3, 5, 7, 9, 11] and look for 7. Look at a number in the middle: 5. It is smaller than 7. The list is sorted, so 1 and 3, which stand to the left of 5, are smaller still. One look removed three numbers. Only 7, 9 and 11 are left. Look at their middle: 9. It is bigger than 7, so 9 and 11 are out. Only 7 is left, and it is the one. Three looks instead of six.
The gain grows with the list. Each look removes about half of what is left. A million sorted log lines shrink to 500,000, then 250,000, then 125,000, and so on. After 19 looks one line is left, and the 20th look checks it. This works only because the list is in order. In an unordered list, a 5 that is smaller than 7 would tell you nothing about its neighbours.
3. The idea
Keep two markers, lo and hi. They mark the part of the array where the target may still be. At the start that is the whole array. Look at the element in the middle between them.
There are three outcomes. If the middle element equals the target, the search is over. If it is smaller than the target, the target can only be to its right, so move lo past the middle. If it is bigger, the target is to its left, so move hi before the middle. Each check removes half of what was left.
One rule protects you from an endless loop: set lo to mid + 1 and hi to mid - 1, and never set them to mid itself. If a marker lands on mid again, the range stops shrinking, and the loop runs forever. Stop when lo becomes bigger than hi. That means the target is not in the array, so return -1.
Trace it on [1, 3, 5, 7, 9, 11], looking for 7.
- Start:
lo = 0,hi = 5. The middle is index 2, which holds 5. That is smaller than 7, solo = 3. - Now
lo = 3,hi = 5. The middle is index 4, which holds 9. That is bigger than 7, sohi = 3. - Now
lo = 3,hi = 3. The middle is index 3, which holds 7. A match.
Found at index 3: three checks instead of six.
4. What it costs
Time is O(log n): each step halves what is left. A million items need about 20 steps, and a billion about 30. That is the almost flat curve from lesson 09: it barely rises as the data grows.
Memory is O(1): three index variables and nothing that grows with the array. There is one condition: the input must already be sorted. If it is not, sorting costs O(n log n). That pays off only when there will be many searches.
5. Where this shows up
Finding a record in sorted logs and time-series stores. A lookup in a B-tree (the structure behind database indexes) is binary search adapted to work with a disk. Finding a version in a sorted list of releases. The lower_bound and upper_bound functions in standard libraries. The git bisect command finds the commit where a bug appeared: it halves the range of commits every time. It is the same idea, applied to a history of versions in place of an array.
binarySearch([1, 3, 5, 7, 9, 11, 13, 15], 13)lo
hi
Find 13 in a sorted array. lo and hi mark the range where it can still be: at first, the whole array.
Bars: the sorted numbers. Binary search halves the range at every comparison; the scan reads them one by one.
One operation is one comparison with the target. Every frame, each lane does the same number of operations, so the slow one falls behind.
O(n)O(log n)
Operations for n numbers, and roughly how long they take at 100 million simple operations a second. An estimate from the same count, not a measurement.
Recognise it
Say these words and it is this pattern
- sorted array
- find a target / first or last occurrence
- n up to 1e6+ and O(log n) expected
- find the boundary where a condition flips
- search a rotated-but-mostly-sorted array (with a twist)
Looks similar, isn't
- Linear search
A scan reads every element but assumes no order. Binary search demands order and reads log n.
- Binary search on a rotated array
A rotation breaks the single sorted run. The check must find the sorted half first.
- Binary search on the answer
There the range is the set of possible answers, not an array. The test is a function you write.
- Binary search tree
A tree halves the same way but through pointers. It also allows inserts, which an array does not.
- Two pointers (opposite ends)
Both ends move there, and the pair being tested changes. Here only one bound moves per step.
n up to 1e9 is fine, because log2 of 1e9 is about 30. Time is O(log n), space is O(1).
On LeetCode
Problems that look up a value in sorted data in O(log n), including a hidden number you can only ask about and a grid sorted row after row. Halve the range with every check.
This is the lesson task, from lo <= hi to the -1 at the end.
A call to guess(mid) checks the middle instead of reading an array.
Binary search the numbers from 1 to num for one whose square is exactly num.
Treat the grid as one sorted list, where position p is row p / n rounded down and column p % n.
Your task (Tier 1)
Write the function binarySearch(nums, target).
The list is sorted from low to high. Look at the number in the middle, then throw away the half where target can't be. Repeat until you find it or run out of numbers. Return the position of target, or -1 if it is not there.
- Input:
nums, a list of numbers sorted from low to high, andtarget, the number you look for. Example:[1, 3, 5, 7, 9, 11]with target7. - Return: the position of
target, or-1if it is not there.
Steps:
- Set
lo = 0(the first position) andhito the last position. - While
lo <= hi, take the middle positionmid. Ifnums[mid]equalstarget, returnmid. Ifnums[mid]is smaller thantarget, search the right half (lo = mid + 1). If it is bigger, search the left half (hi = mid - 1). - If the range becomes empty with no match, return
-1.
Done when all tests pass:
[1, 3, 5, 7, 9, 11], target7->3[1, 2, 3], target1->0(the first number)[1, 2, 3], target3->2(the last number)[1, 2, 3], target4->-1(bigger than every number)[2, 4], target1->-1(smaller than every number)[], target5->-1(empty list, must not crash)[5], target5->0(one number)
Practice
binarySearch
binarySearch(nums, target) -> index (0-based) or -1
export function binarySearch(nums, target) {
// Your code here. The task is described in the box above the editor.
}
Running as a guest
The tests run right here, no account needed. Your code is kept in this browser only. A free account keeps it on every device, marks the step done and shows where you are in the course.
Hint ladder
Stuck? Reveal one rung at a time, but try it yourself first.