Back to Home

Blog

Thoughts, tutorials, and insights

Binary Search Algorithm: Understanding Performance and Implementation

Ilyas Bashirah
Published on 18 Agustus 2025
12 minutes read
AlgorithmsData StructuresBinary SearchPerformanceProgramming

Introduction

Binary search is one of the most fundamental and efficient algorithms in computer science. It’s a search algorithm that finds the position of a target value within a sorted array by repeatedly dividing the search interval in half. Understanding binary search is crucial for any programmer, as it demonstrates the power of divide-and-conquer strategies and logarithmic time complexity.

In this comprehensive guide, we’ll explore the binary search algorithm, analyze its performance characteristics, and work through practical examples to solidify your understanding.

What is Binary Search?

Binary search is a search algorithm that works on the principle of divide and conquer. It operates on sorted arrays and repeatedly narrows down the search space by half until the target element is found or the search space becomes empty.

Key Requirements:

  • Sorted Array: The array must be sorted in ascending or descending order

  • Random Access: The data structure should allow O(1) access to elements by index

  • Comparable Elements: Elements must be comparable using comparison operators

How Binary Search Works

The algorithm follows these steps:

  1. Initialize: Set left = 0 and right = array.length - 1

  2. Calculate Middle: Find mid = (left + right) / 2

  3. Compare:

    • If array[mid] == target, return mid

    • If array[mid] < target, search right half: left = mid + 1

    • If array[mid] > target, search left half: right = mid - 1

  4. Repeat: Continue until left <= right

Visual Example:

Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
Target: 7

Step 1: left=0, right=9, mid=4 → array[4]=9 > 7, search left
Step 2: left=0, right=3, mid=1 → array[1]=3 < 7, search right  
Step 3: left=2, right=3, mid=2 → array[2]=5 < 7, search right
Step 4: left=3, right=3, mid=3 → array[3]=7 = 7, found!

Implementation

Basic Iterative Implementation

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

Recursive Implementation

javascript
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
    if (left > right) {
        return -1; // Base case: target not found
    }
    
    let mid = left + Math.floor((right - left) / 2);
    
    if (arr[mid] === target) {
        return mid; // Found target
    } else if (arr[mid] < target) {
        return binarySearchRecursive(arr, target, mid + 1, right);
    } else {
        return binarySearchRecursive(arr, target, left, mid - 1);
    }
}

TypeScript Implementation with Generics

typescript
function binarySearch<T>(
    arr: T[], 
    target: T, 
    compareFn?: (a: T, b: T) => number
): number {
    let left = 0;
    let right = arr.length - 1;
    
    const compare = compareFn || ((a: T, b: T) => {
        if (a < b) return -1;
        if (a > b) return 1;
        return 0;
    });
    
    while (left <= right) {
        const mid = left + Math.floor((right - left) / 2);
        const comparison = compare(arr[mid], target);
        
        if (comparison === 0) {
            return mid;
        } else if (comparison < 0) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return -1;
}

Performance Analysis

Time Complexity

Best Case: O(1)

  • Target is found at the first comparison (middle element)

Average Case: O(log n)

  • On average, we eliminate half of the remaining elements at each step

Worst Case: O(log n)

  • Target is not in the array, or we need to search until one element remains

Space Complexity

Iterative: O(1)

  • Uses constant extra space regardless of input size

Recursive: O(log n)

  • Each recursive call adds a frame to the call stack

  • Maximum recursion depth is log n

Performance Comparison

Algorithm Time Complexity Space Complexity Best For
Linear Search O(n) O(1) Unsorted data
Binary Search O(log n) O(1) Sorted data
Hash Table O(1) average O(n) Key-value lookups

Practical Performance Example

javascript
// Performance test
function performanceTest() {
    const sizes = [1000, 10000, 100000, 1000000];
    
    sizes.forEach(size => {
        const arr = Array.from({length: size}, (_, i) => i);
        const target = size - 1; // Worst case
        
        // Binary Search
        const start = performance.now();
        binarySearch(arr, target);
        const binaryTime = performance.now() - start;
        
        console.log(`Size: ${size}, Binary Search: ${binaryTime.toFixed(4)}ms`);
    });
}

// Sample output:
// Size: 1000, Binary Search: 0.0123ms
// Size: 10000, Binary Search: 0.0156ms  
// Size: 100000, Binary Search: 0.0189ms
// Size: 1000000, Binary Search: 0.0234ms

Practical Examples and Problems

Example 1: Find Target in Sorted Array

Problem: Given a sorted array and a target value, return the index if found, otherwise return -1.

javascript
function findTarget(nums, target) {
    return binarySearch(nums, target);
}

// Test cases
console.log(findTarget([1, 2, 3, 4, 5], 3)); // Output: 2
console.log(findTarget([1, 2, 3, 4, 5], 6)); // Output: -1

Example 2: Find First Occurrence

Problem: Find the first occurrence of a target in a sorted array with duplicates.

javascript
function findFirstOccurrence(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    let result = -1;
    
    while (left <= right) {
        let mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] === target) {
            result = mid;
            right = mid - 1; // Continue searching left for first occurrence
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return result;
}

// Test case
console.log(findFirstOccurrence([1, 2, 2, 2, 3, 4, 5], 2)); // Output: 1

Example 3: Search Insert Position

Problem: Find the index where target should be inserted to maintain sorted order.

javascript
function searchInsert(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left <= right) {
        let mid = left + Math.floor((right - left) / 2);
        
        if (nums[mid] === target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return left; // Insert position
}

// Test cases
console.log(searchInsert([1, 3, 5, 6], 5)); // Output: 2
console.log(searchInsert([1, 3, 5, 6], 2)); // Output: 1
console.log(searchInsert([1, 3, 5, 6], 7)); // Output: 4

Example 4: Find Peak Element

Problem: Find any peak element in an unsorted array (element greater than neighbors).

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

// Test case
console.log(findPeakElement([1, 2, 3, 1])); // Output: 2 (element 3 is peak)

Advanced Binary Search Variations

1. Lower Bound / Upper Bound

javascript
function lowerBound(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        let mid = left + Math.floor((right - left) / 2);
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return left;
}

function upperBound(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        let mid = left + Math.floor((right - left) / 2);
        if (nums[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return left;
}

2. Binary Search on Answer

javascript
// Find minimum capacity to ship packages in D days
function shipWithinDays(weights, D) {
    let left = Math.max(...weights);
    let right = weights.reduce((sum, w) => sum + w, 0);
    
    function canShipWithCapacity(capacity) {
        let days = 1;
        let currentWeight = 0;
        
        for (let weight of weights) {
            if (currentWeight + weight > capacity) {
                days++;
                currentWeight = weight;
            } else {
                currentWeight += weight;
            }
        }
        
        return days <= D;
    }
    
    while (left < right) {
        let mid = left + Math.floor((right - left) / 2);
        if (canShipWithCapacity(mid)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    
    return left;
}

Common Pitfalls and Best Practices

1. Integer Overflow Prevention

javascript
// ❌ Potential overflow
let mid = (left + right) / 2;

// ✅ Safe calculation  
let mid = left + Math.floor((right - left) / 2);

2. Boundary Conditions

javascript
// Always check edge cases
if (nums.length === 0) return -1;
if (nums.length === 1) return nums[0] === target ? 0 : -1;

3. Loop Termination

javascript
// ✅ Correct condition
while (left <= right) { ... }

// ❌ Infinite loop risk
while (left < right) { 
    // Without proper mid calculation
}

When to Use Binary Search

Use Binary Search When:

  • Data is sorted

  • You need O(log n) search time

  • Memory usage should be minimal

  • You’re searching in a large dataset

Don’t Use Binary Search When:

  • Data is unsorted (unless you can sort it first)

  • Array changes frequently (insertion/deletion heavy)

  • Small datasets (linear search might be faster due to simplicity)

Performance Optimization Tips

1. Cache-Friendly Implementation

javascript
// Better cache locality for very large arrays
function binarySearchCacheFriendly(arr, target) {
    const n = arr.length;
    let left = 0;
    let size = n;
    
    while (size > 0) {
        let half = Math.floor(size / 2);
        let mid = left + half;
        
        if (arr[mid] < target) {
            left = mid + 1;
            size = size - half - 1;
        } else {
            size = half;
        }
    }
    
    return (left < n && arr[left] === target) ? left : -1;
}

2. Branch Prediction Optimization

javascript
// Reduce branch misprediction
function optimizedBinarySearch(arr, target) {
    let left = 0;
    let right = arr.length - 1;
    
    while (left <= right) {
        let mid = left + ((right - left) >> 1); // Bit shift division
        let midVal = arr[mid];
        
        // Use conditional moves instead of branches when possible
        left = midVal < target ? mid + 1 : left;
        right = midVal > target ? mid - 1 : right;
        
        if (midVal === target) return mid;
    }
    
    return -1;
}

Conclusion

Binary search is a powerful algorithm that every programmer should master. Its O(log n) time complexity makes it incredibly efficient for searching sorted data, and its simplicity makes it easy to implement and debug.

Key Takeaways:

  • Time Complexity: O(log n) - exponentially faster than linear search

  • Space Complexity: O(1) for iterative, O(log n) for recursive

  • Requirement: Data must be sorted

  • Applications: Searching, finding boundaries, optimization problems

  • Variations: First/last occurrence, search insert position, binary search on answer

Best Practices:

  • Always prevent integer overflow in mid calculation

  • Handle edge cases properly

  • Choose iterative implementation for better space efficiency

  • Consider cache-friendly variations for very large datasets

The beauty of binary search lies in its simplicity and efficiency. By repeatedly halving the search space, it can find any element in a million-item array with just 20 comparisons. This logarithmic growth makes it indispensable for performance-critical applications.


Tags: #Algorithms #DataStructures #BinarySearch #Performance #Programming #ComputerScience

Related Articles:

  • Understanding Time Complexity and Big O Notation

  • Advanced Array Algorithms and Techniques

  • Divide and Conquer Algorithms Explained