When the input is sorted (or rotated sorted), think binary search. Each step halves the range, so a search over items takes . Most bugs come from two decisions: the loop condition and what the pointers mean when the loop ends. This note fixes both for exact matches, first and last occurrences, and floor and ceiling.
The idea#
Choose the loop condition by what you are looking for.
| Loop condition | When to use it | Post-loop state |
|---|---|---|
l <= r |
Looking for a specific target; the range may be a single element |
The range is empty. Check l or r for the closest value |
l < r |
Narrowing to one index: a "tipping point" or "peak" | l == r. That single element is the answer |
Once the condition is set, one rule of thumb decides what the loop body does:
- Exact match: check
nums[mid] == targetand return. - Closest or threshold match: don't stop early. Let the pointers cross and read the answer from
landr. This squeezes the range down onto the boundary.
The post-loop sandwich#
Letting the pointers cross only helps if you know where they stop. With l <= r, the loop ends only when l = r + 1. If the target isn't present, the pointers have sandwiched the spot where it would be:
rsits on the largest value smaller than the target (the floor).lsits on the smallest value larger than the target (the ceiling), which is also the insertion index.
This works because of an invariant: everything left of l is smaller than the target, and everything right of r is larger. Each step keeps that true, so when the range is empty, r and l are the two neighbors of the gap.
Record and keep going#
The sandwich handles a missing target. Duplicates need one more idea. For the first occurrence, a hit at mid is only a candidate: there may be an earlier one. Note it down and keep searching left. For the last occurrence, note it and search right.
How to spot it#
These cues point to a boundary search.
- Sorted array plus "first", "last", "insert position", "smallest greater than", "largest at most".
- "Return the index where it would be" or "the closest value".
- A yes/no API that flips once (bad version, guess higher or lower).
- An integer answer to a monotone question, such as the floor of a square root.
Template#
One loop covers the exact match and the first or last occurrence. Pass a find_first flag instead of toggling shared state.
l, r = 0, len(nums) - 1
found = -1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
found = mid # a candidate, not yet the edge
if find_first: r = mid - 1 # an earlier copy may exist
else: l = mid + 1 # a later copy may exist
elif nums[mid] < target:
l = mid + 1
else:
r = mid - 1
return found
For a plain exact match, drop found and return on a hit instead. After the loop, r is the floor and l is the ceiling. Either can be off the end (-1 or len(nums)).
Worth exploring: Python's bisect module (bisect_left is the ceiling, bisect_right the first index past the last copy) and patience sorting for LIS (LC 300, 354). Both are natural next steps after boundaries.
Pitfalls#
Off-by-one errors here usually come from one of these.
- Mixing loop styles.
l <= rgoes withr = mid - 1;l < rgoes withr = mid. Mixing them loops forever or skips the answer. - Stopping on the first hit when the question asks for the first or last occurrence.
- Reading
lout of range. After the loop,lcan equallen(nums)andrcan be-1. Guard before indexing. - Overflow guards.
l + (r - l) // 2matters in fixed-width languages; Python integers don't overflow, so(l + r) // 2is fine.
Problems in this pattern#
These problems use this pattern. Solve them before you open the hints below. Start with the first two.
- 704. Binary Search
- 35. Search Insert Position
- 278. First Bad Version
- 374. Guess Number Higher or Lower
- 69. Sqrt(x)
- 744. Find Smallest Letter Greater Than Target
- 34. Find First and Last Position of Element in Sorted Array
- 1300. Sum of Mutated Array Closest to Target
- 4. Median of Two Sorted Arrays
Hints#
Open a hint only after you have tried the problem for a while.
Hint: Binary Search
Use l <= r and return mid on an exact hit. Return -1 once the range is empty.
Hint: Search Insert Position
Let the search fail. The answer is l, which equals r + 1.
Hint: First Bad Version
If mid is bad, everything after it is bad too. Record it and go left; otherwise go right.
Hint: Guess Number Higher or Lower
The API tells you which half to drop. It is an exact-match search where guess(mid) replaces the comparison.
Hint: Find First and Last Position of Element in Sorted Array
Run the template twice, once with find_first true and once false. For [5, 7, 7, 8, 8, 10] and target 8, you get 3 and 4.
Hint: Sqrt(x)
Search the integers 0..x. When the loop ends, r is the floor: r * r is the largest square ≤ x.
Hint: Find Smallest Letter Greater Than Target
Move right on letters[mid] <= target, so l lands on the first letter strictly greater. When l == len(letters), wrap to index 0.
Hint: Closest value to a target
After the loop, compare the candidates at r and l. Guard both ends before you index.
Hint: Sum of Mutated Array Closest to Target
Search the answer space. The loop ends with r as the largest value whose sum is at most the target and l as the smallest value whose sum is above it. Compare the two and prefer the smaller value on a tie. See Binary Search on the Answer.
Hint: Median of Two Sorted Arrays
Binary search a cut in the shorter array so that the two left halves together hold half the elements. The cut is right when each left maximum is at most the other array's right minimum. Use infinities when a side is empty.
Key takeaways#
l <= rfor a specific target;l < rto narrow down to one index.- Exact match: check
mid. Closest or threshold: let the pointers cross. - After
l <= rfails,ris the floor andlis the ceiling (and the insert position). - For first or last occurrence, record the hit and keep searching in that direction.
- Always guard
l == len(nums)andr == -1before indexing.