Binary Search
Search a sorted array in O(log n) — classic template, boundary bugs, and the first-true / last-true variants.
- dsa
- binary-search
- arrays
- interview
- Meta
- Amazon
- Microsoft
- Apple
The problem
Given a sorted array of distinct integers nums and a target, return the index of target, or -1 if missing.
Input: nums = [-1, 0, 3, 5, 9, 12], target = 9
Output: 4
Input: nums = [-1, 0, 3, 5, 9, 12], target = 2
Output: -1
This is the pattern, not just one LC problem. Half of “binary search on answer” interview questions reuse the same loop skeleton.
Brute force
Linear scan O(n). Fine for tiny n; interviewer wants log n when the array is sorted.
function searchLinear(nums: number[], target: number): number {
for (let i = 0; i < nums.length; i++) {
if (nums[i] === target) return i;
}
return -1;
}
Classic binary search
Maintain inclusive window [lo, hi]:
- While
lo ≤ hi mid = lo + ((hi - lo) >> 1)— avoids overflow habits from other languages; in JS still fine- If
nums[mid] === targetreturn mid - If
nums[mid] < target→lo = mid + 1 - Else →
hi = mid - 1 - Return
-1
function search(nums: number[], target: number): number {
let lo = 0;
let hi = nums.length - 1;
while (lo <= hi) {
const mid = lo + ((hi - lo) >> 1);
if (nums[mid] === target) return mid;
if (nums[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1;
}
| Time | O(log n) |
| Space | O(1) |
Why not mid = (lo + hi) / 2?
In JS numbers are floats; for array indices Math.floor((lo + hi) / 2) works for practical sizes. Prefer the shift form or Math.floor explicitly so you never leave a float index.
Template: lower bound (first index ≥ target)
Useful when duplicates exist or you need insert position (LC 35 Search Insert Position).
function lowerBound(nums: number[], target: number): number {
let lo = 0;
let hi = nums.length; // exclusive hi
while (lo < hi) {
const mid = lo + ((hi - lo) >> 1);
if (nums[mid] < target) lo = mid + 1;
else hi = mid;
}
return lo; // in [0, n]
}
Then classic “found?” is lo < n && nums[lo] === target.
Template: binary search on a predicate
When the array is not “find value” but “find minimal capacity / first bad version / peak”:
- Define
feasible(x) → booleanmonotonic:false false … true true - Binary search for the first true
function firstTrue(n: number, feasible: (x: number) => boolean): number {
let lo = 0;
let hi = n; // or domain max + 1
while (lo < hi) {
const mid = lo + ((hi - lo) >> 1);
if (feasible(mid)) hi = mid;
else lo = mid + 1;
}
return lo;
}
Edge cases
- Empty array →
-1 - One element
- Target smaller than all / larger than all
- Even vs odd length (mid bias does not matter if update rules are correct)
- Duplicates — classic distinct version; otherwise specify first/last occurrence
Common bugs
while (lo < hi)withhi = mid - 1inconsistently → infinite loop or missed ends- Using exclusive vs inclusive bounds mixed in one function
- Off-by-one:
hi = nums.lengthvsnums.length - 1 - Integer mid as float: always floor
Rule of thumb: pick one template (inclusive or half-open) and stick to it for the whole interview.
Follow-ups
- Search in Rotated Sorted Array
- Find Minimum in Rotated Sorted Array
- First / last position of element
- Binary search on answer (Koko eating bananas, capacity to ship packages)
Interview delivery
- Confirm sorted + distinct (or not).
- State O(log n).
- Write one clean template.
- Trace target present and absent.
- Mention lower-bound if duplicates come up.
Related
- Search in Rotated Sorted Array
- Find Minimum in Rotated Sorted Array
- Two Sum
- Time Based Key Value Store