Binary Search
You've been doing this your whole life, now let's write it in code
Think about the last time you looked up a word in a physical dictionary. Did you start from page 1 and flip every single page? No, you opened somewhere in the middle, checked if you went too far or not far enough, and adjusted. That instinct IS binary search. You already know this algorithm. We're just going to write it in code.
Imagine I'm thinking of a number between 1 and 1,000,000. You can ask "higher or lower?" after each guess.
Your first guess would be 500,000, right? Not 1. Not 999,999. The middle.
Why? Because one answer, "higher" or "lower", instantly eliminates HALF the possibilities.
This is binary search. In a sorted list of 1,000,000 items, you find anything in at most 20 guesses. Not 1,000,000. Twenty.
1. The middle element IS the target โ done, return the index2. The middle element is TOO SMALL โ target must be to the RIGHT โ move left boundary up3. The middle element is TOO BIG โ target must be to the LEFT โ move right boundary down
That's the whole algorithm. Three decisions. One loop.
Array:
[1, 3, 5, 7, 9, 11, 13, 15] Target: 7Indices: 0 1 2 3 4 5 6 7Step 1: lo=0, hi=7. Middle index = (0+7)//2 = 3.
nums[3] = 7. Is 7 == 7? Yes! Found it. Return 3.That was lucky, let's try a harder one. Target:
11Step 1: lo=0, hi=7. mid=3. `nums[3] = 7`. Is 11 > 7? Yes โ search the RIGHT half. lo = 3+1 = 4Step 2: lo=4, hi=7. mid=5.
nums[5] = 11. Is 11 == 11? Yes! Found it. Return 5.Target:
6 (not in the array)Step 1: lo=0, hi=7. mid=3. `nums[3]=7`. 6 < 7 โ search LEFT. hi = 3-1 = 2Step 2: lo=0, hi=2. mid=1. `nums[1]=3`. 6 > 3 โ search RIGHT. lo = 1+1 = 2Step 3: lo=2, hi=2. mid=2. `nums[2]=5`. 6 > 5 โ lo = 2+1 = 3Step 4: lo=3 > hi=2. Loop ends. Return -1 (not found).
def binary_search(nums, target):
lo, hi = 0, len(nums) - 1 # Start: full array
while lo <= hi: # Keep going while there's still a search space
mid = lo + (hi - lo) // 2 # Middle of current search space
if nums[mid] == target: # Decision 1: Found it!
return mid
elif nums[mid] < target: # Decision 2: Too small โ go right
lo = mid + 1
else: # Decision 3: Too big โ go left
hi = mid - 1
return -1 # Search space collapsed, target not in array
# Try it
nums = [1, 3, 5, 7, 9, 11, 13, 15]
print(binary_search(nums, 11)) # โ 5
print(binary_search(nums, 6)) # โ -1lo + (hi - lo) // 2 instead of (lo + hi) // 2?Both give the same result for normal-sized arrays. But if
lo and hi are both very large numbers (like 2 billion), lo + hi can overflow the integer limit in languages like Java or C++. lo + (hi - lo) // 2 avoids that. In Python integers don't overflow, but it's a good habit to write it the safe way.Standard binary search finds A match, not necessarily the FIRST or LAST one. If you have
[1, 3, 3, 3, 5] and search for 3, you might land on index 1, 2, or 3 depending on the array size.For "find the first occurrence" or "find the last occurrence", you need a modified version, that's the next exercise.
Binary search requires the array to be sorted. What breaks if it's not?
Practice Exercises
0/3 solvedImplement Binary Search
You already play this game every time you look something up in a dictionary, or guess a number. The brain naturally jumps to the middle, not the beginning. Now let's translate that instinct into three lines of code.
Start with the full array in view
Set lo (left boundary) to index 0 and hi (right boundary) to the last index. Your search space is the whole array.
nums = [1, 3, 5, 7, 9, 11, 13, 15] โ lo=0, hi=7Find First Occurrence
[1, 2, 2, 2, 3, 4], target=2 โ 1 (first 2 is at index 1)[1, 3, 5, 7], target=6 โ -1Search in Rotated Array
[1,2,3,4,5,6,7] rotated becomes [4,5,6,7,1,2,3]At any midpoint, one half is always sorted, use that to decide which half could contain your target.
[4,5,6,7,0,1,2], target=0 โ 4[4,5,6,7,0,1,2], target=3 โ -1