Binary Search Bug
Prompt
This is in a small PR adding a helper to look up an item by id in a sorted array — it backs a hot lookup path in our pricing service. A teammate wrote it, tests pass on their machine, and they've asked for your review. Walk me through what you'd flag, and whether you'd approve it.
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
left = mid;
} else {
right = mid - 1;
}
}
return -1;
}
How this round runs
Rank what you find by severity and lead with the finding that actually matters, not the nit. Tell me whether you'd approve it, and what a passing test suite does and doesn't prove here.
Model answer
This is a block, not an approve. Two correctness bugs, ranked:
1 (highest — infinite loop, hangs the request). On the arr[mid] < target
branch the code does left = mid. But mid was just compared and ruled out, so
it should be excluded from the next window with left = mid + 1. Leaving left
at mid means when the window narrows to two adjacent elements, mid keeps
resolving to the same left index, left never advances, and the loop spins
forever. In a hot lookup path that is not a wrong answer — it is a hung request
and a tied-up worker.
Concrete failing case: arr = [1, 2, 3, 4, 5], target = 5.
left=0, right=4 -> mid=2, arr[2]=3 (3 < 5) -> left=2
left=2, right=4 -> mid=3, arr[3]=4 (4 < 5) -> left=3
left=3, right=4 -> mid=3, arr[3]=4 (4 < 5) -> left=3 (spins forever)
2 (correctness — misses the last element). Independently of the loop bug, the
condition left < right exits while one candidate index is still unchecked. The
infinite loop masks this today — those targets hang before they could return
anything — but once left = mid is fixed, a target at the final position of its
window returns -1 (a false "not found"). The condition must be left <= right.
Root cause of both: the window-narrowing invariant is wrong. After comparing
mid, the half that excludes mid must move past it, and the loop must keep
running while the window is non-empty (left <= right). Fix:
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
A note on the green tests: passing CI here proves very little — the existing cases evidently dodge the failing windows (the bug only bites on specific targets), so I'd want the diff to add tests for the last element, an absent value, an empty array, and a single-element array before merging.
I would not block on style — left/right/mid are fine — and I'd say so, so
the author knows the block is about the hang, not preference.
- Led with the infinite loop (hangs a hot path) before the missed-last-element bug — correctness ranked by blast radius, not reading order
- Gave a concrete target that triggers the spin instead of asserting it abstractly
- Named the shared root cause: the window-narrowing invariant, not two unrelated typos
- Called out that green CI proves little here and named the missing test cases
- Explicit approve/block call (block) with a one-line reason
- Would you approve it? → No — block; one of the two bugs hangs the worker. State that before discussing the fix.
- The tests are green — doesn't that mean it's fine? → No; the cases dodge the failing windows. Name the missing ones (last element, absent, empty, single).
- Is `(left + right)` itself a problem? → In JS no (doubles), but in a fixed-width-int language it can overflow; `left + (right - left) / 2` is the portable form.