Binary Search Pattern

1. Classic Binary Search Template O(log n)

Template Type Condition Loop Invariant Use Case
Standard Template while (left <= right) Search space includes both boundaries Find exact target in sorted array
Left Bias while (left < right) Answer in [left, right), terminates at left Find first position meeting condition
Right Bias while (left < right) + mid = left + ⌊(right-left+1)/2⌋ Answer in (left, right], terminates at right Find last position meeting condition
Mid Calculation mid = left + (right - left) / 2 Prevents integer overflow Always use this instead of (left+right)/2

Example: Classic binary search (find exact target)

function binarySearch(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left <= right) {
        const mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] === target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;  // Search right half
        } else {
            right = mid - 1; // Search left half
        }
    }
    
    return -1; // Target not found
}

// Time: O(log n), Space: O(1)
function binarySearchRecursive(nums, target, left = 0, right = nums.length - 1) {
    if (left > right) return -1;
    
    const mid = left + Math.floor((right - left) / 2);
    
    if (nums[mid] === target) return mid;
    
    if (nums[mid] < target) {
        return binarySearchRecursive(nums, target, mid + 1, right);
    } else {
        return binarySearchRecursive(nums, target, left, mid - 1);
    }
}

// Space: O(log n) due to recursion stack

2. Lower Bound and Upper Bound Implementation

Bound Type Definition Template Result
Lower Bound First index where arr[i] >= target while (left < right); if (arr[mid] < target) left = mid+1 Leftmost position to insert target
Upper Bound First index where arr[i] > target while (left < right); if (arr[mid] <= target) left = mid+1 Rightmost position to insert target
First Occurrence Leftmost index of target value Lower bound with equality check Start of range of equal values
Last Occurrence Rightmost index of target value Upper bound - 1 with equality check End of range of equal values

Example: Lower bound (first >= target)

function lowerBound(nums, target) {
    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; // Could be answer
        }
    }
    
    return left;
}

// [1,2,4,4,5], target=4 -> 2
// [1,2,4,4,5], target=3 -> 2

Example: Upper bound (first > target)

function upperBound(nums, target) {
    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;
}

// [1,2,4,4,5], target=4 -> 4
// [1,2,4,4,5], target=3 -> 2

Example: Find first and last position of element

function searchRange(nums, target) {
    const findFirst = () => {
        let left = 0, right = nums.length - 1;
        let result = -1;
        
        while (left <= right) {
            const mid = left + Math.floor((right - left) / 2);
            
            if (nums[mid] === target) {
                result = mid;
                right = mid - 1; // Keep searching left
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return result;
    };
    
    const findLast = () => {
        let left = 0, right = nums.length - 1;
        let result = -1;
        
        while (left <= right) {
            const mid = left + Math.floor((right - left) / 2);
            
            if (nums[mid] === target) {
                result = mid;
                left = mid + 1; // Keep searching right
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return result;
    };
    
    return [findFirst(), findLast()];
}

3. Search in Rotated Sorted Array

Variant Key Insight Strategy Complexity
Rotated Sorted Array One half always sorted Identify sorted half, check if target in range O(log n)
With Duplicates May need to skip duplicates When arr[left]=arr[mid]=arr[right], shrink linearly O(log n) avg, O(n) worst
Find Minimum Minimum at rotation point Compare mid with right, move towards unsorted half O(log n)
Rotation Count Index of minimum element Same as find minimum, return index O(log n)

Example: Search in rotated sorted array

function searchRotated(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left <= right) {
        const mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] === target) return mid;
        
        // Determine which half is sorted
        if (nums[left] <= nums[mid]) {
            // Left half is sorted
            if (nums[left] <= target && target < nums[mid]) {
                right = mid - 1; // Target in left half
            } else {
                left = mid + 1;  // Target in right half
            }
        } else {
            // Right half is sorted
            if (nums[mid] < target && target <= nums[right]) {
                left = mid + 1;  // Target in right half
            } else {
                right = mid - 1; // Target in left half
            }
        }
    }
    
    return -1;
}

// [4,5,6,7,0,1,2], target=0 -> 4

Example: Find minimum in rotated sorted array

function findMin(nums) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] > nums[right]) {
            // Minimum is in right half
            left = mid + 1;
        } else {
            // Minimum is in left half or at mid
            right = mid;
        }
    }
    
    return nums[left];
}

// [3,4,5,1,2] -> 1
// [4,5,6,7,0,1,2] -> 0

4. Binary Search on Answer Space Pattern

Pattern Problem Type Search Space Example Problems
Minimize Maximum Find smallest value where condition holds [min_possible, max_possible] Split array largest sum, minimize max distance
Maximize Minimum Find largest value where condition holds [min_possible, max_possible] Aggressive cows, maximize minimum distance
Feasibility Check Binary search with validation function Test if value is feasible Koko eating bananas, capacity to ship packages
Monotonic Property Answer space has monotone property If k works, all values in direction also work Essential for binary search applicability

Example: Koko eating bananas (minimize eating speed)

function minEatingSpeed(piles, h) {
    // Can Koko finish all piles in h hours at speed k?
    const canFinish = (k) => {
        let hours = 0;
        for (let pile of piles) {
            hours += Math.ceil(pile / k);
        }
        return hours <= h;
    };
    
    let left = 1; // Minimum speed
    let right = Math.max(...piles); // Maximum speed needed
    
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2);
        
        if (canFinish(mid)) {
            right = mid; // Try slower speed
        } else {
            left = mid + 1; // Need faster speed
        }
    }
    
    return left;
}

// piles=[3,6,7,11], h=8 -> 4

Example: Capacity to ship packages within D days

function shipWithinDays(weights, days) {
    // Can ship all packages in 'days' with capacity 'cap'?
    const canShip = (capacity) => {
        let daysNeeded = 1;
        let currentWeight = 0;
        
        for (let weight of weights) {
            if (currentWeight + weight > capacity) {
                daysNeeded++;
                currentWeight = 0;
            }
            currentWeight += weight;
        }
        
        return daysNeeded <= days;
    };
    
    let left = Math.max(...weights); // Min capacity = max weight
    let right = weights.reduce((a, b) => a + b); // Max = sum all
    
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2);
        
        if (canShip(mid)) {
            right = mid; // Try smaller capacity
        } else {
            left = mid + 1; // Need larger capacity
        }
    }
    
    return left;
}

5. Floating Point Binary Search Precision

Aspect Integer Binary Search Floating Point Binary Search
Termination left <= right right - left > epsilon
Precision Exact integer values Threshold like 1e-6 or 1e-9
Iterations log₂(n) Fixed iterations (e.g., 100) or epsilon-based
Mid Update mid = (left + right) / 2 mid = (left + right) / 2.0

Example: Square root with precision

function sqrt(x, precision = 1e-6) {
    if (x === 0) return 0;
    
    let left = 0;
    let right = x;
    
    // For x < 1, sqrt(x) > x
    if (x < 1) right = 1;
    
    while (right - left > precision) {
        const mid = (left + right) / 2;
        const square = mid * mid;
        
        if (square < x) {
            left = mid;
        } else {
            right = mid;
        }
    }
    
    return (left + right) / 2;
}

// sqrt(2) ≈ 1.414214
// sqrt(0.25) ≈ 0.5

Example: Find peak element with fixed iterations

function findPeakElement(nums) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left < right) {
        const mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] < nums[mid + 1]) {
            // Peak is on the right
            left = mid + 1;
        } else {
            // Peak is on the left or at mid
            right = mid;
        }
    }
    
    return left; // Peak index
}

// Works for integer arrays but demonstrates
// the concept of moving towards increasing direction

6. Binary Search Edge Cases and Debugging

Common Bug Symptom Fix Prevention
Infinite Loop Loop never terminates Check mid calculation, ensure progress Use left + (right-left)/2, verify bounds update
Off-by-One Missing boundary elements Review < vs <=, mid±1 Test with arrays of size 1, 2, 3
Integer Overflow Incorrect mid for large arrays Never use (left+right)/2 Always: left + (right-left)/2
Wrong Condition Returns incorrect result Clarify what you're searching for Write invariant: what does [left,right] contain?
Note: Common mistakes to avoid:
  • Using (left + right) / 2 - causes overflow
  • Not maintaining loop invariant
  • Incorrect boundary updates (infinite loop)
  • Forgetting edge cases (empty array, single element)
Warning: Debug checklist:
  • Does loop make progress every iteration?
  • Are boundaries updated correctly?
  • Is mid calculation overflow-safe?
  • Test with edge cases: [1], [1,2], [1,2,3]

Example: Debugging template with invariant comments

function binarySearchWithInvariant(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    
    // Invariant: if target exists, it's in [left, right]
    while (left <= right) {
        // Prevent overflow
        const mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] === target) {
            return mid; // Found
        }
        
        // Maintain invariant: eliminate half that can't contain target
        if (nums[mid] < target) {
            left = mid + 1;  // Target must be in (mid, right]
        } else {
            right = mid - 1; // Target must be in [left, mid)
        }
    }
    
    // Invariant broken: target doesn't exist
    return -1;
}

Binary Search Pattern Summary

  • Classic Template: while(left<=right), mid = left+(right-left)/2
  • Lower Bound: First position >= target, while(left<right)
  • Answer Space: Binary search on result values with feasibility check
  • Rotated Array: Identify sorted half, check if target in range
  • Key Insight: Eliminate half of search space each iteration = O(log n)
Note: Binary search requires monotonic property - some characteristic that increases/decreases across the search space. This applies to sorted arrays (element values increase) and answer spaces (feasibility changes at a threshold).