"Binary search is O(log n)" is a common assumption. But it's not always true.
When you add a feasibility check, handle duplicates, or search in special structures, the complexity changes. Understanding these cases prevents wrong complexity analysis in interviews.
TL;DR
Binary search is NOT always O(log n):
- Binary search on answer: O(log(max-min) × feasibility_cost)
- If feasibility is O(n), total is O(n log m)
- Rotated array with duplicates: O(n) worst case
- 2D matrix (staircase search): O(m + n)
- Finding all occurrences: O(log n + k) where k = count
Scenario 1: Binary Search on Answer with O(n) Check
The Hidden Cost
Problem: Koko Eating Bananas
Analysis:
Binary search iterations: O(log m) where m = max(piles)
Feasibility check per iteration: O(n) where n = len(piles)
Total: O(n log m)Not O(log n)!
Example
def minEatingSpeed(piles, h):
def can_finish(speed):
hours = sum((pile + speed - 1) // speed for pile in piles)
return hours <= h # O(n) operation
left, right = 1, max(piles)
while left < right: # O(log m) iterations
mid = left + (right - left) // 2
if can_finish(mid): # O(n) per iteration
right = mid
else:
left = mid + 1
return left
# Time: O(n log m) where n = len(piles), m = max(piles)
# NOT O(log n)!General Formula
Binary search on answer:
Time = O(log(answer_range) × feasibility_cost)
If feasibility is O(n): Total is O(n log m)
If feasibility is O(n²): Total is O(n² log m)Scenario 2: Rotated Array with Duplicates
The Degradation
Problem: Search in Rotated Sorted Array II (with duplicates)
Analysis:
Best case: O(log n) — no ambiguity
Worst case: O(n) — must check almost all elementsExample
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return True
# When all three are equal, can't eliminate half
if nums[left] == nums[mid] == nums[right]:
left += 1 # O(1) progress
right -= 1 # O(1) progress
elif nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return False
# Worst case: [1, 1, 1, 1, 1, 1, 1, 0, 1]
# Almost all elements are 1, must check most of them
# Time: O(n) worst caseWhy It Happens
Array: [2, 2, 2, 2, 2, 2, 2, 0, 2]
At almost every step:
nums[left] == nums[mid] == nums[right] = 2
Can't determine which half is sorted
Must shrink by 1 element at a time
Result: O(n) timeScenario 3: 2D Matrix Staircase Search
The Linear Complexity
Problem: Search a 2D Matrix II (row-column sorted)
Analysis:
Start from top-right (or bottom-left)
Each step eliminates one row or one column
Worst case: eliminate all rows + all columns
Time: O(m + n)Example
def searchMatrix(matrix, target):
if not matrix or not matrix[0]:
return False
rows, cols = len(matrix), len(matrix[0])
row, col = 0, cols - 1 # Start from top-right
while row < rows and col >= 0:
if matrix[row][col] == target:
return True
elif matrix[row][col] > target:
col -= 1 # Eliminate column
else:
row += 1 # Eliminate row
return False
# Time: O(m + n) where m = rows, n = cols
# NOT O(log(m × n))!Why Not O(log n)?
Matrix is NOT fully sorted:
[1, 4, 7, 11]
[2, 5, 8, 12]
[3, 6, 9, 16]
[10, 13, 14, 17]
Can't treat as 1D sorted array
Must use staircase search: O(m + n)Scenario 4: Finding All Occurrences
The Output-Sensitive Complexity
Problem: Find all indices of target
Analysis:
Find first occurrence: O(log n)
Find last occurrence: O(log n)
Return all indices: O(k) where k = count
Total: O(log n + k)Example
def findAllOccurrences(nums, target):
# Find first occurrence: O(log n)
first = lower_bound(nums, target)
if first == len(nums) or nums[first] != target:
return []
# Find last occurrence: O(log n)
last = upper_bound(nums, target) - 1
# Return all indices: O(k)
return list(range(first, last + 1))
# Time: O(log n + k) where k = number of occurrences
# If k = n (all elements are target), time is O(n)Scenario 5: Binary Search with Expensive Comparison
The Comparison Cost
Problem: Binary search on strings
Analysis:
Binary search iterations: O(log n)
String comparison per iteration: O(L) where L = string length
Total: O(L log n)Example
def binarySearchStrings(strings, target):
left, right = 0, len(strings) - 1
while left <= right:
mid = left + (right - left) // 2
# String comparison is O(L)
if strings[mid] == target:
return mid
elif strings[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# Time: O(L log n) where L = average string length
# NOT O(log n)!Complexity Summary Table
| Scenario | Complexity | Reason |
|---|---|---|
| Classic binary search | O(log n) | Standard |
| Binary search on answer | O(log m × C) | C = feasibility cost |
| Koko Eating Bananas | O(n log m) | Feasibility is O(n) |
| Rotated with duplicates | O(n) worst | Must check all elements |
| 2D staircase search | O(m + n) | Eliminate row or column |
| Find all occurrences | O(log n + k) | k = count |
| String binary search | O(L log n) | L = string length |
How to Calculate True Complexity
Step 1: Identify the Binary Search Component
How many iterations? O(log n) or O(log(max-min))?Step 2: Identify the Per-Iteration Cost
What happens in each iteration?
- Simple comparison? O(1)
- Feasibility check? O(n), O(n²), etc.
- String comparison? O(L)Step 3: Multiply
Total = iterations × per-iteration costExample: Split Array Largest Sum
def splitArray(nums, m):
def can_split(max_sum):
subarrays = 1
current_sum = 0
for num in nums: # O(n)
if current_sum + num > max_sum:
subarrays += 1
current_sum = num
else:
current_sum += num
return subarrays <= m
left, right = max(nums), sum(nums)
while left < right: # O(log(sum - max))
mid = left + (right - left) // 2
if can_split(mid): # O(n)
right = mid
else:
left = mid + 1
return left
# Time: O(n log(sum - max))
# NOT O(log n)!Common Misconceptions
Misconception 1: "Binary search is always O(log n)"
False. Depends on what you're searching and what operations you do per iteration.
Misconception 2: "Binary search on answer is O(log n)"
False. It's O(log(answer_range) × feasibility_cost).
Misconception 3: "All 2D matrix searches are O(log(m×n))"
False. Only fully sorted matrices. Row-column sorted is O(m+n).
FAQ
Q: Is binary search still worth it if it's not O(log n)?
A: Yes! O(n log m) is still better than O(n²) brute force.
Q: How do I know the true complexity?
A: Analyze: iterations × per-iteration cost.
Q: Can I optimize the feasibility check?
A: Sometimes, but often it's inherently O(n). That's ok.
Q: Should I mention this in interviews?
A: Yes! Saying "O(n log m)" shows deeper understanding than "O(log n)".
Conclusion
Binary search isn't always O(log n). The true complexity depends on:
- What you're searching: Answer space, array, matrix
- Per-iteration cost: Feasibility check, comparison cost
- Special cases: Duplicates, output size
Key scenarios:
- Binary search on answer: O(log m × feasibility_cost)
- Rotated with duplicates: O(n) worst case
- 2D staircase: O(m + n)
- Find all: O(log n + k)
How to analyze:
- Count iterations
- Measure per-iteration cost
- Multiply
Understanding these nuances shows interview depth and prevents wrong complexity claims. For more details, see Binary Search on Answer, Rotated Array Duplicates, and the Complete Binary Search Guide.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
