Searching: Linear & Binary
Checking every element, against halving the problem each step. One million elements: a million comparisons, or twenty.
- Trace binary search with lo, mid and hi pointers
- Explain why binary search requires sorted input
- Decide when sorting first is worth it and when it is not
- Write the loop without the two classic bugs
Finish first
Cost at a glance
- linear
- O(n)
- binary
- O(log n)
- binarySpace
- O(1) iterative
- sortThenSearch
- O(n log n + k log n)
Topic contents
Lesson 1 of 4
Linear search, and why it survives
Linear search checks index 0, then 1, and keeps going until it finds the target or runs out of array. It is O(n), it makes no assumptions, and it is the only option on unsorted data. On average it examines half the array when the value is present, and all of it when the value is absent.
It is not a bad algorithm, it is the correct algorithm for a small or unsorted collection. For twenty items the difference against anything cleverer is unmeasurable, and it works on data you cannot sort, such as a stream you are reading once.
Linear and binary search
Run both on the same target and compare the comparison counts, with lo, mid and hi marked and the discarded half dimmed rather than hidden.
Linear and binary search
O(log n) · requires sorted inputStart
Binary search for 68. The array is sorted, which is the whole premise: comparing against any element tells you which side the target must be on.
Lesson 2 of 4
Binary search: halve, don't walk
Binary search keeps a window of indexes that could still contain the target, described by lo and hi. It compares the middle element with the target: equal means done, too small means the target must be to the right so lo moves past mid, too large means the target must be to the left so hi moves before mid. Every comparison discards half the remaining window.
Step it in the lab and watch the counter next to the dimmed cells: those elements were ruled out without ever being read. That is the whole trick, and it is only sound because the array is sorted. On unsorted data the comparison tells you nothing about which side to keep, which is why the lab refuses to run rather than returning a wrong answer.
The number of halvings needed to reduce n to 1 is log₂ n. A thousand elements take at most 10 comparisons, a million take 20, a billion take 30. Adding a thousand times more data costs ten more comparisons.
Lesson 3 of 4
Is sorting first worth it?
Sorting costs O(n log n), so for a single lookup it is not worth it: one linear scan at O(n) is cheaper than sorting to enable a O(log n) search. The calculation changes when you search repeatedly. Sort once at O(n log n), then answer k queries at O(log n) each, and the total is O(n log n + k log n) against O(k·n) for repeated scanning. By the time k is in the thousands the sorted version wins overwhelmingly.
This is the reasoning behind database indexes. An index is a sorted structure maintained alongside the table precisely so that lookups stop being full scans, and the cost is paid on every write instead. If your workload is one write and one read, an index is a waste; at a thousand reads per write it is the difference between a working product and a broken one.
Lesson 4 of 4
The two classic bugs
Binary search is famously easy to get subtly wrong. The first bug is the loop condition: `while (lo < hi)` skips the case where the window has narrowed to one element, so a target sitting there is reported missing. With inclusive bounds the condition must be `lo <= hi`.
The second is the update. Writing `hi = mid` instead of `hi = mid - 1` leaves the already-rejected middle element inside the window, and when lo and hi meet on it the loop stops making progress and spins forever. Because mid has just been compared and rejected, both updates must step past it: `lo = mid + 1` or `hi = mid - 1`.
There is a third, historical bug worth knowing: computing the midpoint as `(lo + hi) / 2` can overflow a fixed-size integer on very large arrays, which is why languages with fixed-width integers use `lo + (hi - lo) / 2`. JavaScript numbers make this harmless in practice, but the same code in Java or C is a real defect.
Worked examples
Read the code, then change it
Copy any example into the playground and break it on purpose. That is the fastest way to learn what each line is holding up.
function binarySearch(sorted, target) {
let lo = 0;
let hi = sorted.length - 1;
let comparisons = 0;
while (lo <= hi) { // <=, or a one-element window is skipped
const mid = lo + Math.floor((hi - lo) / 2); // overflow-safe form
comparisons += 1;
if (sorted[mid] === target) return { index: mid, comparisons };
if (sorted[mid] < target) lo = mid + 1; // step PAST mid
else hi = mid - 1; // step PAST mid
}
return { index: -1, comparisons };
}
const data = Array.from({ length: 1000 }, (_, i) => i * 3);
console.log(binarySearch(data, 2997)); // near the end, still ~10 comparisons
console.log(binarySearch(data, 4)); // absent
// Linear search on the same lookup, for contrast:
let linear = 0;
for (const value of data) { linear += 1; if (value === 2997) break; }
console.log('linear comparisons:', linear);Practice
Work these out yourself
No answer key here on purpose: these are the questions worth thinking through before you move on. Open one and work it out.
0/4 attemptedAssessment
Check your understanding
Answer each question, then read the explanation. That is where the learning is.
Finished this topic?
0 of 4 lessons marked done. Marking the topic complete ticks the rest.
