The Binary Search Playbook: Every Variation You'll Ever See in Interviews
Binary search breaks every student's confidence at some point. This guide gives you one template to rule them all, then shows you every variation — first/last occurrence, rotated arrays, search on answer space — with working code.
Binary search sounds simple. "Search a sorted array in O(log n)." Every student knows that. Yet binary search problems have one of the highest failure rates in interviews — even among students who've solved hundreds of problems.
The reason: there are dozens of variations, and each one breaks the naive implementation in a subtle way. Off-by-one errors, infinite loops, and wrong comparisons are incredibly easy to introduce.
This guide gives you one correct template and then shows you exactly how to adapt it for every variation.
Why Binary Search is Hard to Get Right
Consider this: for the classic binary search, there are 4 decisions that can each go wrong independently:
- Loop condition:
while left < rightorwhile left <= right? - Midpoint:
mid = (left + right) // 2ormid = left + (right - left) // 2? - Left update:
left = midorleft = mid + 1? - Right update:
right = midorright = mid - 1?
That's 16 combinations for basic binary search. Only a few are correct. The rest cause either wrong answers or infinite loops.
The solution: commit to one template and understand why every choice is made.
The Master Template
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2 # prevents integer overflow
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1 # target is in the right half
else:
right = mid - 1 # target is in the left half
return -1 # target not found
Every decision justified:
left <= right: We need to check whenleft == right(single element). Using<would skip it.mid = left + (right - left) // 2: Equivalent to(left + right) // 2but avoids integer overflow for large arrays. Always use this form.left = mid + 1: We already checkedmidand it's not the target. Don't recheck it.right = mid - 1: Same reasoning — don't recheckmid.
Memorise this. Every variation below is a controlled modification of this base.
Variation 1: Find First Occurrence
Given a sorted array with possible duplicates, find the index of the first occurrence of target. Return -1 if not found.
def find_first(arr, target):
left, right = 0, len(arr) - 1
result = -1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
result = mid # found one — but keep searching left
right = mid - 1 # ← key change: narrow right to find earlier occurrence
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return result
Key insight: When you find the target, don't stop. Record it as a candidate answer, then keep searching the left half for an earlier occurrence.
Variation 2: Find Last Occurrence
def find_last(arr, target):
left, right = 0, len(arr) - 1
result = -1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
result = mid # found one — but keep searching right
left = mid + 1 # ← key change: narrow left to find later occurrence
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return result
Combining both: LeetCode "Find First and Last Position" = [find_first(arr, target), find_last(arr, target)]. Two separate binary searches.
Variation 3: Find Insertion Position
Find the index where target should be inserted to keep the array sorted (even if target doesn't exist).
def search_insert(arr, target):
left, right = 0, len(arr) # ← right = len(arr), not len(arr)-1
while left < right: # ← strict less-than
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid # ← right = mid, not mid - 1
return left
Why different? We're no longer searching for an element — we're finding a position. The invariant is: left is always a valid insertion point. When the loop ends, left == right is the answer.
This is also Python's bisect_left behavior.
Variation 4: Search in Rotated Sorted Array
A sorted array has been rotated at an unknown pivot (e.g., [4,5,6,7,0,1,2]). Find the index of target.
This is a classic medium problem. The key insight: even in a rotated array, one half is always sorted.
def search_rotated(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
# Determine which half is sorted
if nums[left] <= nums[mid]:
# Left half is sorted
if nums[left] <= target < nums[mid]:
right = mid - 1 # target is in sorted left half
else:
left = mid + 1 # target must be in right half
else:
# Right half is sorted
if nums[mid] < target <= nums[right]:
left = mid + 1 # target is in sorted right half
else:
right = mid - 1 # target must be in left half
return -1
Trace through [4,5,6,7,0,1,2], target=0:
- left=0, right=6, mid=3, nums[3]=7
- nums[0]=4 ≤ nums[3]=7 → left half sorted
- Is 4 ≤ 0 < 7? No → left = mid + 1 = 4
- left=4, right=6, mid=5, nums[5]=1
- nums[4]=0 ≤ nums[5]=1 → left half sorted
- Is 0 ≤ 0 < 1? Yes → right = mid - 1 = 4
- left=4, right=4, mid=4, nums[4]=0 == target → return 4 ✓
Variation 5: Find Minimum in Rotated Sorted Array
Find the minimum element in a rotated sorted array.
def find_min_rotated(nums):
left, right = 0, len(nums) - 1
while left < right: # ← stops when left == right (one element)
mid = left + (right - left) // 2
if nums[mid] > nums[right]:
# Minimum is in the right half (not mid)
left = mid + 1
else:
# Minimum is in the left half (could be mid)
right = mid # ← don't exclude mid
return nums[left]
Why compare to nums[right] not nums[left]? If nums[mid] > nums[right], the rotation point (minimum) must be in [mid+1, right]. If nums[mid] <= nums[right], minimum is in [left, mid].
Variation 6: Binary Search on the Answer Space
This is the most powerful and underused binary search technique. Instead of searching an array, you search the space of possible answers.
Pattern: When a problem asks "find the minimum/maximum X such that condition Y is satisfied."
Example: Koko Eating Bananas
Koko can eat at most
kbananas/hour. She hashhours. Find the minimumkthat lets her eat all piles.
def min_eating_speed(piles, h):
def can_finish(speed):
hours_needed = sum((pile + speed - 1) // speed for pile in piles)
return hours_needed <= h
left, right = 1, max(piles) # answer must be in [1, max_pile]
while left < right:
mid = left + (right - left) // 2
if can_finish(mid):
right = mid # mid works — try smaller speed
else:
left = mid + 1 # mid doesn't work — need faster speed
return left
The template for "search on answer":
left, right = minimum_possible_answer, maximum_possible_answer
while left < right:
mid = left + (right - left) // 2
if feasible(mid):
right = mid # mid works, try smaller
else:
left = mid + 1 # mid doesn't work, try larger
return left
More problems using this template:
- Minimum Days to Make m Bouquets: Binary search on number of days
- Capacity to Ship Packages in D Days: Binary search on ship capacity
- Find the Smallest Divisor Given a Threshold: Binary search on divisor
Variation 7: Find Peak Element
A peak element is an element greater than its neighbours. Find any peak in O(log n).
def find_peak(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] > nums[mid + 1]:
# Descending — peak is at mid or to its left
right = mid
else:
# Ascending — peak is to the right of mid
left = mid + 1
return left
When to Use Binary Search: The Decision Checklist
Binary search applies when you have monotonicity — a function that transitions from False to True (or True to False) exactly once.
Ask yourself:
- Is the data sorted? → Standard binary search
- Is there a rotated sorted array? → Variation 4 or 5
- Does the problem ask for first/last of something? → Variation 1 or 2
- Does the problem ask for minimum/maximum X such that some condition holds? → Binary search on answer space
- Does the problem have a clear "feasibility" function and a bounded answer range? → Binary search on answer space
Common Bugs and How to Avoid Them
| Bug | Symptom | Fix |
|---|---|---|
| right = len(arr) instead of len(arr)-1 | Index out of bounds | Use len(arr)-1 for element search |
| mid = (left + right) // 2 | Integer overflow (rare in Python) | Use left + (right - left) // 2 |
| left = mid instead of mid + 1 | Infinite loop | Always use mid + 1 when moving left pointer past mid |
| right = mid instead of mid - 1 in standard search | Infinite loop or wrong answer | Use mid - 1 in standard search; mid only when searching for position |
| Not handling empty array | Crash or wrong output | Check if not arr: return -1 before starting |
Practice Problems by Difficulty
Easy (get these perfect first):
- Binary Search (LeetCode #704)
- Search Insert Position (#35)
- First Bad Version (#278)
Medium (the real interview questions):
- Find First and Last Position (#34)
- Search in Rotated Sorted Array (#33)
- Find Minimum in Rotated Sorted Array (#153)
- Koko Eating Bananas (#875)
- Capacity to Ship Packages (#1011)
Hard (differentiate yourself):
- Median of Two Sorted Arrays (#4)
- Split Array Largest Sum (#410)
Start now: Open Practice and filter by "Binary Search". Solve the Easys today with the template above — resist looking at solutions until you've tried for 20 minutes.
Ready to practice what you just learned?
Apply these concepts with AI-powered tools built for CS students.