Binary Search β `<=` vs `<` Boundary Conditions
Binary Search β <= vs < Boundary Conditions
The golden rule: Ask β “Has this index been fully eliminated as a candidate?”
- Yes β skip it: use
mid + 1/mid - 1- No β keep it: use
mid
1. The Two Styles of Binary Search
Every binary search loop falls into one of two mental models. The choice of <= vs < follows directly from which model you pick.
Style A β Explicit check, shrink past mid
while (L <= R) { // L and R are both live candidates
int mid = L + (R - L) / 2;
if (arr[mid] == target) return mid; // checked explicitly
if (arr[mid] < target) L = mid + 1; // mid ruled out β skip it
else R = mid - 1; // mid ruled out β skip it
}
// loop ends when search space is empty (L > R)
Because mid is tested in the if, it is definitively ruled out. So mid Β± 1 is safe β you won’t skip the answer.
Style B β Answer lives at boundary, keep mid
while (L + 1 < R) { // answer is always inside [L, R]
int mid = L + (R - L) / 2;
if (arr[mid] <= target) L = mid; // mid not ruled out β keep it
else R = mid; // mid not ruled out β keep it
}
// loop ends at L + 1 == R β check both
if (arr[L] == target) return L;
if (arr[R] == target) return R;
mid is never checked directly inside the loop, so you cannot skip it. The loop guarantees that exactly two candidates remain at termination.
2. What Each Pointer Means (The Invariant)
Understanding the invariant of each pointer is more reliable than thinking about edge cases one by one.
| Pointer | Invariant in Style B |
|---|---|
L | Last index that is definitely β€ target β still a candidate because we haven’t verified it equals the target |
R | First index that is definitely > target β still a candidate, it could be the answer |
Once you know what each pointer guarantees, the boundary checks write themselves.
3. Condition Boundaries β <= vs < Inside the Loop
The same rule applies when choosing whether to include endpoints in range checks.
| Situation | Operator | Reason |
|---|---|---|
target could equal nums[L] | >= (include) | nums[L] is still a candidate β not yet ruled out |
target vs nums[mid] (already checked above) | < (exclude) | nums[mid] was tested on the line above β definitively ruled out |
target could equal nums[R] | <= (include) | nums[R] is still a candidate |
Example β Rotated Sorted Array
if (nums[L] <= nums[mid]) {
// left half is sorted
if (target >= nums[L] && target < nums[mid])
// ^^ ^
// nums[L] is live nums[mid] already checked
// β include with >= β exclude with <
R = mid - 1;
else
L = mid + 1;
}
4. Common Mistakes Decoded
| Mistake | Why it breaks |
|---|---|
while (L < R) with R = mid - 1 | Skips mid before checking it β can miss the answer when L == R is the target |
while (L <= R) with R = mid | If arr[mid] keeps being chosen as R, L never catches up β infinite loop |
Range check uses < on both boundaries | Misses the case where target equals the left boundary exactly |
Range check uses <= on mid | Re-examines a mid that was already tested β can push into the wrong half |
5. Quick Decision Checklist
Before writing any boundary operator, ask these questions in order:
Has this index already been explicitly checked?
- Yes (e.g.
nums[mid] == targetwas already tested) β use</mid Β± 1 - No β use
<=/mid
- Yes (e.g.
Can the target equal this boundary value?
- Yes β use
>=or<=to include it - No β use
>or<to exclude it
- Yes β use
Which loop style am I using?
- Style A (
while L <= R) β always usemid Β± 1 - Style B (
while L + 1 < R) β always use plainmid
- Style A (
One-line test: “Has this index already been definitively eliminated?” β Yes:
<ormid Β± 1. No:<=ormid.