Binary search looks simple, but most mistakes come from a few small details:
Problem: sorted array, find target, return index or -1.
This is the most standard version.
function search(nums: number[], target: number): number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
Because here your search space is a closed interval [left, right], both ends are included.
At the start:
left = 0
right = nums.length - 1
That means every valid index is inside the interval.
If only one element remains, for example:
left = 3, right = 3
there is still one candidate to check, so loop should continue.
That is why we use:
while (left <= right)
If you used:
while (left < right)
then when left === right, you would stop too early and might miss the last element.
Example:
nums = [5]
target = 5
left = 0, right = 0
left < right is false immediately
loop never runs
wrong answer
So for exact search in closed interval, <= is the safe default.
Because after checking nums[mid], you already know whether mid is correct.
If:
nums[mid] < target
then mid cannot be the answer, and everything left of it also cannot be answer.
So next range becomes:
[mid + 1, right]
not [mid, right].
Similarly, if:
nums[mid] > target
then next range is:
[left, mid - 1]
not [left, mid].
A very common bug is writing:
left = mid;
or
right = mid;
in this exact-search pattern.
That can cause infinite loop.
Example:
nums = [1, 3]
target = 3
Suppose:
left = 0
right = 1
mid = 0
If nums[mid] < target and you write:
left = mid;
then left stays 0, nothing changes, and loop can get stuck.
For 704 style, remember:
Most common:
const mid = Math.floor((left + right) / 2);
This is fine in JavaScript and TypeScript for interview use.
You may also see:
const mid = left + Math.floor((right - left) / 2);
Why this version exists:
in some languages like Java/C++, left + right may overflow, left + (right - left) / 2 avoids overflow
In JavaScript, integer overflow here is usually not the practical issue for LeetCode arrays, but many people still prefer the second form because it is language-safe as a habit.
So interview-wise, either is okay in TS, but this is a nice habit:
const mid = left + Math.floor((right - left) / 2);
This usually appears when your search space is a half-open interval:
[left, right)
right is excluded.
Or when doing boundary search, where you want loop to end with left === right as the final answer.
A classic example is 35. Search Insert Position.
We want the position where target should go, even if target does not exist.
A clean way is:
function searchInsert(nums: number[], target: number): number {
let left = 0;
let right = nums.length; // note: not nums.length - 1
while (left < right) {
const mid = left + Math.floor((right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
Notice two big differences:
right = nums.length
loop is left < right
That means search space is:
[left, right)
right is not included.
At the end, when left === right, the interval is empty, and that index is exactly the insertion point.
Because this is not exact-match search anymore.
We are looking for the first index where nums[i] >= target.
If:
nums[mid] >= target
then mid might still be the answer.
So we cannot throw it away.
That is why:
right = mid;
not:
right = mid - 1;
This is one of the biggest binary search mindset shifts:
That one idea decides whether you write:
right = mid - 1
or right = mid
Ask this every time:
After checking mid, is mid still a possible answer?
If no:
left = mid + 1
right = mid - 1
If yes:
left = mid
right = mid
But be careful: if you keep mid, you usually also need the correct loop form, often while (left < right).
This problem is great because it shows boundary binary search.
You usually solve it by finding:
Find first position of target
function lowerBound(nums: number[], target: number): number {
let left = 0;
let right = nums.length;
while (left < right) {
const mid = left + Math.floor((right - left) / 2);
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
This returns the first index where value is >= target.
Then:
function searchRange(nums: number[], target: number): number[] {
const start = lowerBound(nums, target);
const end = lowerBound(nums, target + 1) - 1;
if (start === nums.length || nums[start] !== target) {
return [-1, -1];
}
return [start, end];
}
This is a super useful pattern.