Chapter 10

Searching & Binary Search

Master the most fundamental algorithms for finding elements. From linear search to advanced binary search variations, binary search on answer, and specialized techniques—comprehensive coverage with 45+ C# code examples and 11 complete LeetCode problem solutions.

7
Search Algorithms
45+
Code Examples
11
LeetCode Problems
3
Binary Search Templates
Table of Contents
10.1

Linear Search

Linear search is the simplest search algorithm: examine elements one by one until you find the target or reach the end. It's the baseline for understanding search efficiency and serves as the foundation for grasping why binary search is so powerful.

Basic Concept & When to Use

Linear search works on any array, sorted or unsorted. You start at one end and check each element sequentially. While slow for large datasets, it's often the right choice for small arrays or when data isn't sorted.

Standard Linear Search
Iterate through array, comparing each element with target. Return index on match; return -1 if not found.
O(n) Time O(1) Space
1
C#
public static int LinearSearch(int[] arr, int target)
{
    for (int i = 0; i < arr.Length; i++)
    {
        if (arr[i] == target)
            return i;
    }
    return -1;
}
Time
O(n)
Space
O(1)
Best Case
O(1)
Worst Case
O(n)

Explanation: We iterate through each element. Best case: target is at index 0 (one comparison). Worst case: target is at the end or doesn't exist (n comparisons). Average case: target found at position n/2 (n/2 comparisons), still O(n).

Sentinel Linear Search Optimization

A clever optimization: place a copy of the target at the end of the array (a "sentinel"). This eliminates the need to check array bounds on each iteration—the loop always terminates when it finds the sentinel.

C#
public static int SentinelLinearSearch(int[] arr, int target)
{
    int last = arr[arr.Length - 1];
    arr[arr.Length - 1] = target; // Place sentinel

    int i = 0;
    while (arr[i] != target)
        i++;

    arr[arr.Length - 1] = last; // Restore original value

    // If we found sentinel at position length-1, target wasn't in array
    if (i == arr.Length - 1 && last != target)
        return -1;

    return i;
}

This approach reduces memory traffic (fewer comparisons with array bounds) and can be 10-20% faster in practice, though the asymptotic complexity remains O(n).

Search in Unsorted vs Sorted Arrays

Unsorted Array
  • No optimization possible beyond sentinel
  • Must check every element (no early termination)
  • Always O(n)
  • Use linear search
Sorted Array
  • Can stop early when target is smaller than current element
  • Still O(n) worst case, but typically faster in practice
  • Binary search is O(log n) — far superior
  • Always use binary search for sorted data
When to use linear search: Arrays with fewer than 100 items, or when the cost of sorting exceeds the benefit. For larger sorted arrays, binary search is dramatically faster.
10.2

Binary Search Fundamentals

Binary search is a masterclass in algorithmic thinking. By eliminating half the search space each iteration, it reduces O(n) to O(log n)—a stunning improvement. For 1 million items, binary search needs at most 20 comparisons.

Prerequisites & How It Works

Binary search requires a sorted array. The algorithm works by maintaining two pointers (left and right) and repeatedly checking the middle element:

1
Initialize pointers: Set left = 0, right = array length - 1
2
Check middle: Compute mid and compare arr[mid] with target
3
Eliminate half: If match found, return mid. If arr[mid] < target, move left pointer right. Otherwise, move right pointer left.
4
Repeat: Continue until left > right (element not found) or element is found

Step-by-Step Walkthrough Example

Let's search for target = 7 in array: [1, 3, 5, 7, 9, 11, 13]

Walkthrough
Array: [1, 3, 5, 7, 9, 11, 13]
Target: 7

Iteration 1:
  left=0, right=6, mid=3
  arr[3]=7 equals target
  Found at index 3! Return 3

Another example with target = 6 (not in array):

Walkthrough
Array: [1, 3, 5, 7, 9, 11, 13]
Target: 6

Iteration 1: left=0, right=6, mid=3
  arr[3]=7 > target, so right=2

Iteration 2: left=0, right=2, mid=1
  arr[1]=3 < target, so left=2

Iteration 3: left=2, right=2, mid=2
  arr[2]=5 < target, so left=3

left > right: Exit loop, return -1

Iterative Implementation

Binary Search (Iterative)
Use a while loop with left/right pointers. Straightforward, no recursion overhead, most efficient.
O(log n) Time O(1) Space
2
C#
public static int BinarySearch(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length - 1;

    while (left <= right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
            return mid;

        if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return -1;
}

Recursive Implementation

C#
public static int BinarySearchRecursive(int[] arr, int target, int left, int right)
{
    if (left > right)
        return -1;

    int mid = left + (right - left) / 2;

    if (arr[mid] == target)
        return mid;

    if (arr[mid] < target)
        return BinarySearchRecursive(arr, target, mid + 1, right);

    return BinarySearchRecursive(arr, target, left, mid - 1);
}

// Call: BinarySearchRecursive(arr, target, 0, arr.Length - 1)

Critical: The Overflow Bug — Why left + (right - left) / 2?

This is a classic gotcha that trips up even experienced developers. Consider calculating the midpoint:

❌ Naive (Can Overflow)
int mid = (left + right) / 2;
Problem: If left and right are large positive integers (near int.MaxValue), their sum overflows. For instance, if left = 2,000,000,000 and right = 1,500,000,000, the sum exceeds int.MaxValue and wraps around to a negative number.
✓ Correct (No Overflow)
int mid = left + (right - left) / 2;
Why it works: We compute the offset (right - left) first, which is always smaller and never overflows. Then add to left. This is mathematically equivalent but avoids overflow.
⚠️
Always use left + (right - left) / 2. This is so important that Java's Arrays.binarySearch uses it, and a bug in Google's implementation went unnoticed for years.

Complexity Analysis

Time Complexity: O(log n)
The search space halves each iteration. After k iterations, we have n/2^k elements left. When n/2^k = 1, solving for k gives k = log₂(n). For 1,000,000 items, we need at most log₂(1,000,000) ≈ 20 iterations.
Space Complexity
Iterative: O(1) — No extra space beyond loop variables.
Recursive: O(log n) — Call stack depth equals recursion depth (log n levels).
Prefer iterative over recursive: Iterative binary search is slightly faster (no recursion overhead) and uses O(1) space. Use recursion only when the problem naturally requires it (e.g., tree traversal).
10.3

Binary Search Variations

Standard binary search finds any occurrence of the target. Often, you need specific variations: find the leftmost position, find the rightmost, or find an insertion point. These variations build on the core algorithm with subtle but critical changes.

Find First Occurrence (Leftmost)

When the array has duplicates, find the index of the first (leftmost) occurrence of the target.

C#
public static int FindFirstOccurrence(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length - 1;
    int result = -1;

    while (left <= right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
        {
            result = mid;
            right = mid - 1; // Continue searching left half for earlier occurrence
        }
        else if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return result;
}

Key difference: When we find a match, we don't return immediately. Instead, we save it and continue searching the left half to find an earlier occurrence.

Find Last Occurrence (Rightmost)

Find the index of the last (rightmost) occurrence of the target.

C#
public static int FindLastOccurrence(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length - 1;
    int result = -1;

    while (left <= right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
        {
            result = mid;
            left = mid + 1; // Continue searching right half for later occurrence
        }
        else if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return result;
}

Find Insertion Point (Lower & Upper Bound)

Find the position where the target should be inserted to keep the array sorted. If the target exists, lower bound returns where it begins; upper bound returns where it ends.

C#
// Lower bound: smallest index where arr[i] >= target
public static int LowerBound(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length;

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid;
    }

    return left;
}

// Upper bound: smallest index where arr[i] > target
public static int UpperBound(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length;

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] <= target)
            left = mid + 1;
        else
            right = mid;
    }

    return left;
}

Built-in Binary Search: Array.BinarySearch() & List.BinarySearch()

C# provides built-in binary search methods. These return the index if found, or a negative number indicating where to insert if not found.

C#
int[] arr = new int[] { 1, 3, 5, 7, 9 };

// Array.BinarySearch
int index = Array.BinarySearch(arr, 5);
// index = 2 (found at position 2)

index = Array.BinarySearch(arr, 6);
// index = -4 (not found; would insert at ~-4 = 3)

// List.BinarySearch
List<int> list = new List<int> { 1, 3, 5, 7, 9 };
index = list.BinarySearch(5);
// index = 2 (found)

Search in Rotated Sorted Array

An array is rotated when a portion at the end is moved to the front. Example: [4, 5, 6, 7, 0, 1, 2] is [0, 1, 2, 4, 5, 6, 7] rotated. The trick: one half is always sorted.

C#
public static int SearchRotatedArray(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length - 1;

    while (left <= right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
            return mid;

        // Determine which half is sorted
        if (arr[left] <= arr[mid])
        {
            // Left half is sorted
            if (arr[left] <= target && target < arr[mid])
                right = mid - 1;
            else
                left = mid + 1;
        }
        else
        {
            // Right half is sorted
            if (arr[mid] < target && target <= arr[right])
                left = mid + 1;
            else
                right = mid - 1;
        }
    }

    return -1;
}

Find Minimum in Rotated Sorted Array

In a rotated sorted array, the minimum is the "rotation point." Use binary search to find it in O(log n).

C#
public static int FindMin(int[] arr)
{
    int left = 0;
    int right = arr.Length - 1;

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        // If mid is greater than right, minimum is in the right part
        if (arr[mid] > arr[right])
            left = mid + 1;
        else
            right = mid;
    }

    return arr[left];
}

Search a 2D Matrix

A matrix where each row is sorted left-to-right and the first element of each row is greater than the last element of the previous row. Treat it as a flattened 1D sorted array.

C#
public static bool SearchMatrix(int[][] matrix, int target)
{
    int rows = matrix.Length;
    int cols = matrix[0].Length;

    int left = 0;
    int right = rows * cols - 1;

    while (left <= right)
    {
        int mid = left + (right - left) / 2;
        int row = mid / cols;
        int col = mid % cols;
        int midValue = matrix[row][col];

        if (midValue == target)
            return true;

        if (midValue < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return false;
}

Find Peak Element

A peak is an element greater than its neighbors. Find any peak in O(log n) time (not necessarily the maximum).

C#
public static int FindPeak(int[] arr)
{
    int left = 0;
    int right = arr.Length - 1;

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        // If mid is less than its right neighbor, peak is to the right
        if (arr[mid] < arr[mid + 1])
            left = mid + 1;
        else
            right = mid;
    }

    return left;
}
10.4

Binary Search on Answer

This is a paradigm shift: instead of searching for an element in an array, we binary search on the solution space itself. The key insight is recognizing when a feasible answer exists, then searching for the optimal one.

Concept: Binary Search on Solution Space

Template: "Can we achieve X? If yes, try a smaller X. If no, try a larger X." This requires:

Example: Koko Eating Bananas (LeetCode 875)

Problem: Koko has n piles of bananas. She wants to finish eating all bananas in h hours. She can choose an eating speed k (bananas per hour). In each hour, she eats k bananas from one pile; if a pile has fewer than k bananas, she eats them all in one hour and waits. Find the minimum speed k such that she finishes in h hours.

C#
public static int MinEatingSpeed(int[] piles, int h)
{
    int left = 1;
    int right = piles.Max();

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        if (CanFinish(piles, h, mid))
            right = mid; // Try slower speeds
        else
            left = mid + 1; // Need faster speed
    }

    return left;
}

private static bool CanFinish(int[] piles, int h, int speed)
{
    long hoursNeeded = 0;
    foreach (int pile in piles)
    {
        hoursNeeded += (pile + speed - 1) / speed; // Ceiling division
    }
    return hoursNeeded <= h;
}

How it works: Search space is [1, max_pile]. For any speed, we can calculate hours needed. If feasible, try a slower speed. If infeasible, try faster. Time: O(n log max_pile).

Example: Split Array Largest Sum (LeetCode 410)

Problem: Split an array into m subarrays such that the maximum sum of any subarray is minimized. Find this minimized maximum sum.

C#
public static int SplitArray(int[] nums, int m)
{
    int left = nums.Max();
    int right = nums.Sum();

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        if (CanSplit(nums, m, mid))
            right = mid;
        else
            left = mid + 1;
    }

    return left;
}

private static bool CanSplit(int[] nums, int m, int maxSum)
{
    int splits = 1;
    long currentSum = 0;

    foreach (int num in nums)
    {
        if (currentSum + num <= maxSum)
            currentSum += num;
        else
        {
            splits++;
            currentSum = num;
            if (splits > m) return false;
        }
    }

    return true;
}

Example: Capacity to Ship Packages (LeetCode 1011)

Problem: Ship packages over d days. Each day, load packages with max weight of capacity c in order (can't skip). Find minimum capacity.

C#
public static int ShipWithinDays(int[] weights, int days)
{
    int left = weights.Max();
    int right = weights.Sum();

    while (left < right)
    {
        int mid = left + (right - left) / 2;

        if (CanShip(weights, days, mid))
            right = mid;
        else
            left = mid + 1;
    }

    return left;
}

private static bool CanShip(int[] weights, int days, int capacity)
{
    int daysUsed = 1;
    long currentLoad = 0;

    foreach (int w in weights)
    {
        if (currentLoad + w <= capacity)
            currentLoad += w;
        else
        {
            daysUsed++;
            currentLoad = w;
            if (daysUsed > days) return false;
        }
    }

    return true;
}
Recognition pattern: Problems asking for "minimum of maximum" or "maximum of minimum" are often binary-search-on-answer. Look for feasibility checks.
10.5

Ternary Search & Interpolation Search

Ternary Search

Ternary search works on unimodal functions (functions with a single peak or valley). Instead of dividing by 2, divide by 3. Time complexity: O(log₃ n) ≈ O(log n), slightly worse than binary search theoretically, but sometimes better in practice for specific patterns.

C#
// Find maximum in a unimodal array
public static int TernarySearchMax(int[] arr)
{
    int left = 0;
    int right = arr.Length - 1;

    while (right - left > 2)
    {
        int mid1 = left + (right - left) / 3;
        int mid2 = right - (right - left) / 3;

        if (arr[mid1] > arr[mid2])
            right = mid2 - 1;
        else
            left = mid1 + 1;
    }

    int maxVal = arr[left];
    for (int i = left + 1; i <= right; i++)
        maxVal = Math.Max(maxVal, arr[i]);

    return maxVal;
}

Interpolation Search

For uniformly distributed data, interpolation search can beat binary search. Instead of guessing the midpoint, estimate where the target is based on its value distribution.

C#
public static int InterpolationSearch(int[] arr, int target)
{
    int left = 0;
    int right = arr.Length - 1;

    while (left <= right && target >= arr[left] && target <= arr[right])
    {
        // Estimate position based on value
        int pos = left + (int)((double)(right - left) / (arr[right] - arr[left]) * (target - arr[left]));

        if (arr[pos] == target)
            return pos;

        if (arr[pos] < target)
            left = pos + 1;
        else
            right = pos - 1;
    }

    return -1;
}

Average Time: O(log log n) for uniformly distributed data. Worst Case: O(n) if data is skewed. Rarely used in practice; binary search is safer and almost always faster.

10.6

Exponential & Jump Search

Exponential Search

Useful for unbounded or infinite arrays. First, find a range [2^k, 2^(k+1)) containing the target via exponential jumps. Then binary search within that range.

C#
public static int ExponentialSearch(int[] arr, int target)
{
    if (arr[0] == target)
        return 0;

    // Find range [2^i, 2^(i+1))
    int i = 1;
    while (i < arr.Length && arr[i] < target)
        i *= 2;

    // Binary search in range
    int left = i / 2;
    int right = Math.Min(i, arr.Length - 1);

    while (left <= right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
            return mid;
        if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return -1;
}

Time: O(log n). Use when: Array size is unknown, or you're searching an infinite stream.

Jump Search

Jump Search divides the array into blocks of size sqrt(n). Jump through blocks, then linear search within the found block. Time: O(sqrt(n)).

C#
public static int JumpSearch(int[] arr, int target)
{
    int n = arr.Length;
    int step = (int)Math.Sqrt(n);
    int prev = 0;

    // Jump through blocks
    while (arr[Math.Min(step, n) - 1] < target)
    {
        prev = step;
        step += (int)Math.Sqrt(n);
        if (prev >= n)
            return -1;
    }

    // Linear search in block [prev, step)
    while (arr[prev] < target)
    {
        prev++;
        if (prev == Math.Min(step, n))
            return -1;
    }

    if (arr[prev] == target)
        return prev;

    return -1;
}
10.7

Complete Practice Problems with Solutions

Problem 1: Binary Search (LeetCode 704)

Statement: Given a sorted array and target, return the index if found, else -1.

C#
public class Solution
{
    public int Search(int[] nums, int target)
    {
        int left = 0, right = nums.Length - 1;

        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return -1;
    }
}
// Time: O(log n), Space: O(1)

Problem 2: First Bad Version (LeetCode 278)

Statement: Given an API isBadVersion(version), find the first bad version in [1, n].

C#
public class Solution : VersionControl
{
    public int FirstBadVersion(int n)
    {
        int left = 1, right = n;

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (IsBadVersion(mid))
                right = mid; // First bad is at mid or before
            else
                left = mid + 1; // First bad is after mid
        }
        return left;
    }
}
// Time: O(log n), Space: O(1)

Problem 3: Search Insert Position (LeetCode 35)

Statement: Find the index where target should be inserted to keep array sorted.

C#
public class Solution
{
    public int SearchInsert(int[] nums, int target)
    {
        int left = 0, right = nums.Length;

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target)
                left = mid + 1;
            else
                right = mid;
        }
        return left;
    }
}
// Time: O(log n), Space: O(1)

Problem 4: Find First and Last Position (LeetCode 34)

Statement: Find the range [first, last] of target in sorted array. Return [-1, -1] if not found.

C#
public class Solution
{
    public int[] SearchRange(int[] nums, int target)
    {
        int first = FindFirst(nums, target);
        if (first == -1) return new int[] { -1, -1 };

        int last = FindLast(nums, target);
        return new int[] { first, last };
    }

    private int FindFirst(int[] nums, int target)
    {
        int left = 0, right = nums.Length - 1;
        int result = -1;

        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target)
            {
                result = mid;
                right = mid - 1;
            }
            else if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return result;
    }

    private int FindLast(int[] nums, int target)
    {
        int left = 0, right = nums.Length - 1;
        int result = -1;

        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target)
            {
                result = mid;
                left = mid + 1;
            }
            else if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return result;
    }
}
// Time: O(log n), Space: O(1)

Problem 5: Search in Rotated Sorted Array (LeetCode 33)

Statement: Search in rotated sorted array with distinct elements.

C#
public class Solution
{
    public int Search(int[] nums, int target)
    {
        int left = 0, right = nums.Length - 1;

        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;

            if (nums[left] <= nums[mid])
            {
                if (nums[left] <= target && target < nums[mid])
                    right = mid - 1;
                else
                    left = mid + 1;
            }
            else
            {
                if (nums[mid] < target && target <= nums[right])
                    left = mid + 1;
                else
                    right = mid - 1;
            }
        }
        return -1;
    }
}
// Time: O(log n), Space: O(1)

Problem 6: Find Minimum in Rotated Sorted Array (LeetCode 153)

C#
public class Solution
{
    public int FindMin(int[] nums)
    {
        int left = 0, right = nums.Length - 1;

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right])
                left = mid + 1;
            else
                right = mid;
        }
        return nums[left];
    }
}
// Time: O(log n), Space: O(1)

Problem 7: Search a 2D Matrix (LeetCode 74)

C#
public class Solution
{
    public bool SearchMatrix(int[][] matrix, int target)
    {
        int rows = matrix.Length, cols = matrix[0].Length;
        int left = 0, right = rows * cols - 1;

        while (left <= right)
        {
            int mid = left + (right - left) / 2;
            int val = matrix[mid / cols][mid % cols];

            if (val == target) return true;
            if (val < target) left = mid + 1;
            else right = mid - 1;
        }
        return false;
    }
}
// Time: O(log(m*n)), Space: O(1)

Problem 8: Koko Eating Bananas (LeetCode 875)

(See detailed solution in Section 10.4)

Problem 9: Find Peak Element (LeetCode 162)

C#
public class Solution
{
    public int FindPeakElement(int[] nums)
    {
        int left = 0, right = nums.Length - 1;

        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] < nums[mid + 1])
                left = mid + 1;
            else
                right = mid;
        }
        return left;
    }
}
// Time: O(log n), Space: O(1)

Problem 10: Sqrt(x) (LeetCode 69)

Statement: Compute the integer square root of x.

C#
public class Solution
{
    public int MySqrt(int x)
    {
        if (x == 0) return 0;

        long left = 1, right = x;

        while (left <= right)
        {
            long mid = left + (right - left) / 2;
            if (mid * mid == x) return (int)mid;
            if (mid * mid < x) left = mid + 1;
            else right = mid - 1;
        }

        return (int)right;
    }
}
// Time: O(log x), Space: O(1)

Problem 11: Median of Two Sorted Arrays (LeetCode 4) — Hard

Statement: Find the median of two sorted arrays. Time: O(log(min(m,n))).

C#
public class Solution
{
    public double FindMedianSortedArrays(int[] nums1, int[] nums2)
    {
        if (nums1.Length > nums2.Length)
            return FindMedianSortedArrays(nums2, nums1);

        int m = nums1.Length, n = nums2.Length;
        int left = 0, right = m;

        while (left <= right)
        {
            int cut1 = left + (right - left) / 2;
            int cut2 = (m + n + 1) / 2 - cut1;

            int left1 = (cut1 == 0) ? int.MinValue : nums1[cut1 - 1];
            int left2 = (cut2 == 0) ? int.MinValue : nums2[cut2 - 1];
            int right1 = (cut1 == m) ? int.MaxValue : nums1[cut1];
            int right2 = (cut2 == n) ? int.MaxValue : nums2[cut2];

            if (left1 <= right2 && left2 <= right1)
            {
                if ((m + n) % 2 == 0)
                    return (Math.Max(left1, left2) + Math.Min(right1, right2)) / 2.0;
                else
                    return Math.Max(left1, left2);
            }
            else if (left1 > right2)
                right = cut1 - 1;
            else
                left = cut1 + 1;
        }

        return -1.0;
    }
}
// Time: O(log(min(m,n))), Space: O(1)
10.8

Binary Search Template Summary

Template 1: Exact Match

Find an exact match or return -1. Used when any occurrence works.

C#
int left = 0, right = arr.Length - 1;
while (left <= right)
{
    int mid = left + (right - left) / 2;
    if (arr[mid] == target) return mid;
    if (arr[mid] < target) left = mid + 1;
    else right = mid - 1;
}
return -1;

Template 2: Left Boundary (First Occurrence)

Find the leftmost position of target. Use when duplicates exist.

C#
int left = 0, right = arr.Length;
while (left < right)
{
    int mid = left + (right - left) / 2;
    if (arr[mid] < target)
        left = mid + 1;
    else
        right = mid;
}
// left is now the leftmost position where arr[left] >= target

Template 3: Right Boundary (Last Occurrence)

Find the rightmost position of target.

C#
int left = 0, right = arr.Length;
while (left < right)
{
    int mid = left + (right - left) / 2;
    if (arr[mid] <= target)
        left = mid + 1;
    else
        right = mid;
}
// left - 1 is now the rightmost position where arr[left-1] <= target

Decision Tree: Which Template to Use?

Template 1
Use when: Finding any occurrence of target
Return: Index of target or -1
Loop: while (left <= right)
Key: Return immediately on match
Template 2 & 3
Use when: Finding boundary positions (first/last)
Return: left (or left-1)
Loop: while (left < right)
Key: Shrink search space, return pointer
10.9

Interview Tips & Common Pitfalls

Recognizing Binary Search Opportunities

Not every problem needs binary search, but these signals suggest it might:

1
Sorted input: If the array is sorted or can be treated as sorted, binary search often applies.
2
Optimization problem: "Find minimum of maximum" or "maximize minimum"? Likely binary search on answer.
3
Feasibility check exists: Can you verify if a candidate answer is valid? That's your binary search condition.
4
Efficiency required: If a naive approach is O(n²) but the problem has large constraints (n > 10⁵), binary search to O(n log n) or O(log n) usually works.

Edge Cases Checklist

Always Test
  • Empty array: Should return -1
  • Single element: Match or no match
  • Target at boundaries: First/last element
  • Duplicates: First, last, or any occurrence
  • Large numbers: Test overflow scenarios
Common Bugs
  • Using (left + right) / 2 (overflow)
  • Off-by-one errors in boundaries
  • Infinite loops (wrong loop condition)
  • Forgetting to check if element exists before returning index

How to Avoid Infinite Loops

The most common bug in binary search is infinite loops. Here's how to prevent them:

⚠️
Rule 1: Every iteration must shrink the search space. If mid = left or mid = right, you'll have infinite loops.
Rule 2: Use left < right (not <=) in boundary-finding templates. Use left <= right in exact-match templates.
Rule 3: When updating pointers: left = mid + 1 or right = mid - 1 (not = mid).

Communication Strategy in Interviews

1
State the problem clearly: "I need to find the first occurrence of target in a sorted array."
2
Explain the approach: "I'll use binary search to achieve O(log n) time. When I find the target, I'll search left for the first occurrence."
3
Walk through an example: Show left/right/mid values on a small array to demonstrate correctness.
4
Discuss complexity: "Time: O(log n), Space: O(1)." Be ready to optimize further if asked.

Interview Tips

Tip 1
Always Verify Assumptions
Ask: Is the array sorted? Are there duplicates? Can there be negative numbers? Don't assume—clarify with the interviewer.
Tip 2
Code Template First
Memorize the three templates. Write one from memory, then adapt it. This saves time and reduces bugs significantly.
Tip 3
Test Your Code
Trace through your code on edge cases before claiming it's done. Interviewers expect this rigor and attention to detail.