Binary Search
Imagine you're guessing a number between 1 and 100. Instead of starting at 1 and going up, you guess 50. Too high? Now you only care about 1–49. Guess 25. Too low? Now it's 26–49. Each guess cuts your options exactly in half.
That's binary search in a nutshell. Instead of checking every item one by one, you always check the middle of what's left — and throw away the half that can't contain your answer.
The catch: the list must be sorted. Without sorting, you can't know which half to discard. Sorting algorithms like merge sort or quicksort can prepare the data in \(O(n \log n)\).
Why it's so fast
After each comparison, half the remaining elements are gone. After 10 comparisons, you've narrowed a list of 1,024 down to just 1. After 30 comparisons, you've searched through one billion elements. That's the power of \(O(\log n)\).
The iterative template
Keep two pointers: lo (left boundary) and hi (right boundary). At each step, compute mid = lo + (hi - lo) / 2 — always write it this way to avoid integer overflow. If arr[mid] equals your target, you're done. If it's too small, move lo = mid + 1. If it's too large, move hi = mid - 1. Loop while lo <= hi.
Finding first or last occurrence
When there are duplicates, don't stop at the first match. To find the leftmost occurrence: when arr[mid] == target, record the index and keep searching left with hi = mid - 1. For the rightmost, record and go right with lo = mid + 1. This is the pattern behind C++'s lower_bound and upper_bound.