Senior 19 min · March 05, 2026

Binary Search Overflow — The Bug Hidden in Java for 9 Years

Binary search breaks silently on large arrays when mid calculation overflows.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Everything here is grounded in real deployments.

Follow
Production
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Core concept: halve the search space every step by comparing target with middle element
  • Requires sorted data — unsorted input gives silently wrong answers, no exception, no warning
  • Works in O(log n) time: for 1 million items, at most 20 comparisons; for 1 billion, at most 30
  • Two implementations: iterative (while loop, O(1) space) and recursive (function call overhead, O(log n) stack space)
  • Biggest pitfall: overflow in (left + right) / 2 — use left + (right - left) / 2 or (left + right) >>> 1
  • Production insight: silently returns -1 for existing targets if array is unsorted — always sort first and validate
  • Java built-ins: Arrays.binarySearch() for primitives, Collections.binarySearch() for Lists — both require pre-sorted input
✦ Definition~90s read
What is Binary Search Algorithm?

Binary search is an algorithm that finds the position of a target value within a sorted array in O(log n) time by repeatedly dividing the search interval in half. It exists because linear search—checking each element one by one—scales linearly with input size, becoming unusable for large datasets (e.g., searching a billion records would take a billion steps).

Imagine you're looking up the word 'Mango' in a physical dictionary.

Binary search solves this by exploiting sorted order: it compares the target to the middle element, then discards the half of the array that cannot contain the target, halving the search space each iteration. This makes it foundational for databases, search engines, and any system requiring fast lookups on sorted data.

Where binary search fits in the ecosystem: it's the go-to for sorted array lookups, but you should not use it on unsorted data (it will fail), on linked lists (where random access is O(n)), or when the dataset is small enough that linear search's simplicity wins. Real-world implementations appear in Java's Arrays.binarySearch() and Collections.binarySearch(), Python's bisect module, and C++'s std::lower_bound.

The infamous bug that hid in Java for 9 years was an integer overflow in the midpoint calculation (low + high) / 2—when low + high exceeded Integer.MAX_VALUE (about 2.1 billion), it wrapped to a negative number, causing incorrect behavior or infinite loops. This bug, discovered in 2006, affected any binary search on arrays larger than half the maximum integer size, a scenario rare but catastrophic when hit.

The fix—using low + (high - low) / 2—avoids overflow entirely and is now the standard pattern across all languages.

Plain-English First

Imagine you're looking up the word 'Mango' in a physical dictionary. You don't start at page 1 and read every word — you crack the book open near the middle, see you're at 'J', and think 'M comes after J, so I'll look in the right half.' You keep halving the remaining pages until you land on 'Mango'. That's binary search — instead of checking every item one by one, you repeatedly cut the search space in half. What might take 1,000 steps the slow way takes just 10 steps with binary search.

Binary search halves your problem space on every check. It's the difference between guessing a number between 1 and 1,000 in ten tries versus a thousand. Get the logic wrong, and you'll ship a bug that silently corrupts data for years—exactly what happened in Java's own library for nearly a decade.

What Binary Search Actually Does — and the One-Off Bug That Hid for Nearly a Decade

Binary search finds an element in a sorted array by repeatedly halving the search interval. Given a sorted list of n elements, it compares the target against the middle element, then discards the half that cannot contain the target. This reduces the search space from n to n/2 to n/4 … in O(log n) comparisons — exponentially faster than linear scan’s O(n).

In practice, binary search works only on sorted data and random-access structures (arrays, array-backed lists). Each iteration computes mid = (low + high) / 2. The classic Java implementation used (low + high) / 2, which overflows when low + high exceeds Integer.MAX_VALUE — a bug that silently corrupts results for large arrays (size > 2^30). The fix: mid = low + (high - low) / 2.

Use binary search whenever you need fast lookups on static or rarely-changed sorted data — database index seeks, dictionary lookups, versioned data snapshots. It’s the foundation of every balanced tree and B-tree operation. Misunderstanding the overflow edge case has caused production outages in search engines, trading systems, and distributed caches.

The Overflow Trap
The classic (low + high) / 2 overflows for arrays larger than 2^30 elements — a bug that went undetected in Java’s standard library for nine years.
Production Insight
A trading system crashed during peak hours when a sorted price-level array exceeded 1 billion entries, causing binary search to return garbage indices and corrupt order matching.
The symptom: intermittent ArrayIndexOutOfBoundsException or silent data corruption on arrays > 2^30 elements.
Rule of thumb: always compute mid as low + (high - low) / 2 — it’s overflow-safe and costs one extra subtraction.
Key Takeaway
Binary search is O(log n) only on random-access, sorted data — linked lists kill the guarantee.
The overflow bug in mid calculation is real and has caused production failures; always use the safe formula.
Binary search is not just a search algorithm — it’s the core of every balanced tree, B-tree, and database index.
Binary Search Overflow Bug in Java THECODEFORGE.IO Binary Search Overflow Bug in Java Hidden bug from mid calculation overflow in sorted array search Binary Search Algorithm Divide sorted array, compare mid to target Mid Calculation (low+high)/2 Classic formula but overflow risk for large arrays Integer Overflow Sum exceeds Integer.MAX_VALUE, becomes negative Fix: low + (high-low)/2 Safe mid calculation avoids overflow ⚠ Using (low+high)/2 causes overflow for large arrays Always use low + (high - low) / 2 to prevent bug THECODEFORGE.IO
thecodeforge.io
Binary Search Overflow Bug in Java
Binary Search Algorithm

Before binary search makes sense, you need to feel the pain it solves. The naive way to search a list is linear search — start at item 1, check it, move to item 2, check it, and keep going until you find your target or run out of items. For a list of 10 things, that's fine. For a list of 1,000,000 things, you could be checking up to one million items. In the absolute worst case, your target is the very last item, or it isn't there at all.

This approach is called O(n) in Big O notation — as the list grows, your work grows at exactly the same rate. Double the data, double the time.

Binary search flips the script. Instead of O(n), it runs in O(log n) time. For a list of 1,000,000 items, binary search needs at most 20 comparisons — not one million. For 1 billion items, at most 30 comparisons. The catch? Your data must be sorted first. That single requirement is what makes the halving trick possible: if your list is sorted, you can always look at the middle and know with certainty which half your target lives in.

Here's the critical subtlety most tutorials skip: if you only search once, sorting first costs O(n log n) plus O(log n) for the search, totalling O(n log n) — worse than a single O(n) linear scan. Binary search pays off when you sort once and search many times. For k searches on a sorted array, total cost is O(n log n + k log n). Linear search for k queries costs O(kn). Binary search wins when k is large enough that k log n < kn, which is true for any k ≥ 1 after sorting.

io/thecodeforge/search/LinearSearchExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.thecodeforge.search;

public class LinearSearchExample {

    // Check every element one-by-one until we find the target
    public static int findWithLinearSearch(int[] sortedScores, int targetScore) {
        for (int index = 0; index < sortedScores.length; index++) {
            // Did we find the target at this position?
            if (sortedScores[index] == targetScore) {
                return index; // Return the position where it was found
            }
        }
        return -1; // -1 is the convention for "not found"
    }

    public static void main(String[] args) {
        int[] sortedScores = {10, 22, 35, 47, 58, 63, 79, 81, 94, 99};
        int targetScore = 79;

        int result = findWithLinearSearch(sortedScores, targetScore);

        if (result != -1) {
            System.out.println("Score " + targetScore + " found at index " + result);
            System.out.println("Linear search checked " + (result + 1) + " element(s) to get there.");
        } else {
            System.out.println("Score " + targetScore + " was not found.");
        }
    }
}
Output
Score 79 found at index 6
Linear search checked 7 element(s) to get there.
The Sorting Prerequisite
Binary search only works on sorted data. If you try to run it on an unsorted array, it will confidently return wrong answers — no error, no exception, just silent incorrectness. Always verify your data is sorted before applying binary search. In Java: for (int i=1; i<arr.length; i++) assert arr[i-1] <= arr[i];
Production Insight
In interviews, candidates rush to code binary search without checking if the array is sorted.
Real production bug: a log-processing pipeline sorted timestamps descending, but binary search assumed ascending. The search returned -1 for records that existed because the comparison logic was inverted relative to the sort order.
Rule: always document and test the sorting direction before searching. A descending-sorted array needs flipped comparison operators in the binary search, or reverse the array first.
Key Takeaway
Binary search needs sorted input.
Without it, the algorithm is not just slow — it's wrong.
Sort first, search second.
One search on unsorted data → linear scan is cheaper than sort + binary search.

How Binary Search Actually Works — The Step-by-Step Logic

Here's the core idea, plain and simple. You have a sorted array. You want to find a target value. Binary search maintains two pointers — a left boundary and a right boundary — that represent the section of the array still worth searching. At the start, left is index 0 and right is the last index.

Each round, you calculate the middle index of the current window. You look at the value sitting at that middle index and ask one of three questions: 1. Is the middle value equal to my target? If yes — you're done, return the index. 2. Is the middle value less than my target? Then the target must be in the right half. Move left up to middle + 1. 3. Is the middle value greater than my target? Then the target must be in the left half. Move right down to middle - 1.

You repeat this until either you find the target or left exceeds right (meaning the target isn't in the array at all).

Each iteration eliminates half the remaining candidates. Starting with 1,000,000 items: after step 1 you have 500,000 candidates. After step 2, 250,000. After 20 steps, you're down to 1. That's the magic of logarithms — log base 2 of 1,000,000 is about 20.

Notice the boundary updates use mid + 1 and mid - 1, not just mid. This is critical. If you use left = mid when the target is greater than arr[mid], and the window has only 2 elements (left and left+1), then mid = left and you set left = mid = left — the window never shrinks, and you loop forever.

io/thecodeforge/search/BinarySearchIterative.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package io.thecodeforge.search;

public class BinarySearchIterative {

    /**
     * Searches for targetScore inside a sorted array using binary search.
     * Returns the index of the target if found, or -1 if not present.
     */
    public static int binarySearch(int[] sortedScores, int targetScore) {
        int leftBoundary = 0;                        // Start of the search window
        int rightBoundary = sortedScores.length - 1; // End of the search window
        int stepCount = 0;                           // Just for demonstration

        while (leftBoundary <= rightBoundary) {
            stepCount++;

            // Calculate midpoint — see the Gotchas section for WHY we write it this way
            int middleIndex = leftBoundary + (rightBoundary - leftBoundary) / 2;
            int middleValue = sortedScores[middleIndex];

            System.out.println("Step " + stepCount + ": Checking index " + middleIndex
                    + " (value = " + middleValue + ")  |  window: ["
                    + leftBoundary + ", " + rightBoundary + "]");

            if (middleValue == targetScore) {
                // Bullseye — target is right here
                System.out.println("Found " + targetScore + " at index "
                        + middleIndex + " in " + stepCount + " step(s).\n");
                return middleIndex;

            } else if (middleValue < targetScore) {
                // Middle value is too small — target must be in the RIGHT half
                leftBoundary = middleIndex + 1;

            } else {
                // Middle value is too big — target must be in the LEFT half
                rightBoundary = middleIndex - 1;
            }
        }

        // Left has crossed right — target is not in the array
        System.out.println(targetScore + " was not found after " + stepCount + " step(s).\n");
        return -1;
    }

    public static void main(String[] args) {
        int[] sortedScores = {10, 22, 35, 47, 58, 63, 79, 81, 94, 99};

        System.out.println("--- Searching for 63 ---");
        binarySearch(sortedScores, 63);

        System.out.println("--- Searching for 50 (not in array) ---");
        binarySearch(sortedScores, 50);

        System.out.println("--- Searching for 10 (first element) ---");
        binarySearch(sortedScores, 10);
    }
}
Output
--- Searching for 63 ---
Step 1: Checking index 4 (value = 58) | window: [0, 9]
Step 2: Checking index 7 (value = 81) | window: [5, 9]
Step 3: Checking index 5 (value = 63) | window: [5, 6]
Found 63 at index 5 in 3 step(s).
--- Searching for 50 (not in array) ---
Step 1: Checking index 4 (value = 58) | window: [0, 9]
Step 2: Checking index 1 (value = 22) | window: [0, 3]
Step 3: Checking index 2 (value = 35) | window: [2, 3]
Step 4: Checking index 3 (value = 47) | window: [3, 3]
50 was not found after 4 step(s).
--- Searching for 10 (first element) ---
Step 1: Checking index 4 (value = 58) | window: [0, 9]
Step 2: Checking index 1 (value = 22) | window: [0, 3]
Step 3: Checking index 0 (value = 10) | window: [0, 0]
Found 10 at index 0 in 3 step(s).
Trace It By Hand First
Before trusting your code, pick any sorted array of 8 numbers and trace through binary search on paper with your finger. Mark the left, right, and middle positions each round. Doing this once physically wires the algorithm into your brain faster than re-reading ever will.
Production Insight
First attempt at binary search often misses the condition left <= right — using left < right skips the last element when the search window narrows to exactly one element.
In production, this means missing records at the start or end of the dataset, causing intermittent false negatives that are maddening to debug.
Rule: use <= to ensure the single-element window is checked before the loop exits.
Key Takeaway
Loop while left <= right.
Update boundaries with mid + 1 / mid - 1 — never mid alone.
Use safe midpoint formula: left + (right - left) / 2.
Each step halves the search space — log₂(1,000,000) ≈ 20 steps.

Binary Search the Recursive Way — Same Logic, Different Shape

The iterative version uses a while loop. The recursive version calls itself with a smaller window each time. Both approaches are identical in logic and performance — it's a style choice, though iterative is usually preferred in production because it avoids stack overhead.

In the recursive version, you pass the current left and right boundaries as parameters. When you hit the base case (left > right), you return -1. When you find the target, you return its index. Otherwise you call yourself again with the updated half.

Seeing both versions is valuable because many interview questions ask you to implement one or the other, and understanding recursion here is a perfect stepping stone to harder recursive algorithms like merge sort and quicksort.

Notice how the recursive version reads almost like the English description of binary search — it's self-documenting. The tradeoff is that each recursive call adds a frame to the call stack. For an array of one million elements, that's at most 20 recursive calls deep, which is perfectly fine. For extremely large arrays (billions of elements), the stack depth reaches ~30 frames — still well within default stack limits. But it's good to be aware of the difference, especially in languages without tail-call optimisation (Java, Python).

io/thecodeforge/search/BinarySearchRecursive.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package io.thecodeforge.search;

public class BinarySearchRecursive {

    /**
     * Recursive binary search.
     * Each call works on a shrinking window defined by leftBoundary and rightBoundary.
     */
    public static int binarySearchRecursive(
            int[] sortedTemperatures,
            int targetTemp,
            int leftBoundary,
            int rightBoundary) {

        // Base case: the window is empty — target is not in the array
        if (leftBoundary > rightBoundary) {
            return -1;
        }

        // Find the midpoint of the current window
        int middleIndex = leftBoundary + (rightBoundary - leftBoundary) / 2;
        int middleValue = sortedTemperatures[middleIndex];

        if (middleValue == targetTemp) {
            // Found it — return the position
            return middleIndex;

        } else if (middleValue < targetTemp) {
            // Target is in the right half — recurse with updated left boundary
            return binarySearchRecursive(
                    sortedTemperatures, targetTemp, middleIndex + 1, rightBoundary);

        } else {
            // Target is in the left half — recurse with updated right boundary
            return binarySearchRecursive(
                    sortedTemperatures, targetTemp, leftBoundary, middleIndex - 1);
        }
    }

    // Convenience wrapper so the caller doesn't need to pass initial boundaries
    public static int search(int[] sortedTemperatures, int targetTemp) {
        return binarySearchRecursive(
                sortedTemperatures, targetTemp, 0, sortedTemperatures.length - 1);
    }

    public static void main(String[] args) {
        int[] weeklyHighTemps = {-5, 0, 3, 7, 12, 18, 21, 26, 30};

        int searchTarget = 18;
        int foundAt = search(weeklyHighTemps, searchTarget);

        if (foundAt != -1) {
            System.out.println("Temperature " + searchTarget
                    + "°C found at index " + foundAt);
        } else {
            System.out.println("Temperature " + searchTarget + "°C not in records.");
        }

        // Try a value that doesn't exist
        int missingTemp = 15;
        int missingResult = search(weeklyHighTemps, missingTemp);
        System.out.println("Temperature " + missingTemp
                + "°C found at index: " + missingResult
                + " (−1 means not found)");
    }
}
Output
Temperature 18°C found at index 5
Temperature 15°C found at index: -1 (−1 means not found)
Interview Gold
Interviewers love asking 'can you write both the iterative and recursive versions?' Having both ready — and knowing the tradeoff (O(log n) stack frames for recursive vs O(1) space for iterative) — instantly signals you understand the algorithm deeply, not just by memory.
Production Insight
Recursive binary search on large arrays in languages without tail-call optimisation (Java, Python, JavaScript) can theoretically cause StackOverflowError in extreme cases, though binary search's O(log n) depth makes this practically impossible for array-based search. The real risk is if you accidentally write non-halving recursion.
In production, prefer iterative to avoid unpredictable stack depth and to make debugging easier — iterative loops are simpler to trace in debuggers and log statements.
Rule: recursive is fine for interviews and readability; iterative for production systems.
Key Takeaway
Recursive = elegant, self-documenting, mirrors the English description.
Iterative = safer for production, O(1) space, easier to debug.
Know both. Prefer iterative in production code.

Common Mistakes That Break Binary Search (And Exactly How to Fix Them)

Binary search looks simple, but it has a handful of classic traps that catch even experienced developers. The most infamous is the integer overflow bug — it lived undetected in Java's standard library for nearly a decade before being reported by Joshua Bloch in 2006. Understanding these mistakes doesn't just help you avoid bugs; it shows interviewers you think carefully about edge cases.

The second most common mistake is applying binary search to unsorted data. The algorithm will run without throwing any errors — it will just silently return the wrong answer. There's no warning, no exception. This is the sneakiest kind of bug because your tests on small sorted datasets pass, and the failure only manifests in production when unsorted data slips through.

Third: getting the boundary updates wrong. Using middleIndex instead of middleIndex + 1 when updating the left boundary causes an infinite loop when your window is two elements wide. The loop never terminates because the window never shrinks. Specifically: when left=2 and right=3, mid=2 (integer division), and setting left = mid gives left=2 again — no progress.

Fourth: using left < right instead of left <= right. This skips the case where the window narrows to exactly one element. If your target happens to be that single remaining element, the loop exits before checking it, and you return -1 incorrectly.

Let's look at each of these concretely in code so you can recognize them on sight.

io/thecodeforge/search/BinarySearchPitfalls.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package io.thecodeforge.search;

public class BinarySearchPitfalls {

    // ============================================================
    // MISTAKE 1: Integer overflow when calculating the midpoint
    // ============================================================

    public static int buggyMidpoint(int left, int right) {
        // WRONG: (left + right) can overflow if both are large integers
        // e.g. left = 1_500_000_000, right = 1_600_000_000
        // Their sum = 3_100_000_000 which exceeds Integer.MAX_VALUE (2_147_483_647)
        // Java wraps to -1_194_967_296, divided by 2 = -597_483_648
        return (left + right) / 2; // <-- DANGER ZONE
    }

    public static int safeMidpoint(int left, int right) {
        // CORRECT: This formula gives the same result but never overflows
        // because we add a small delta (right - left) to left, not two large numbers
        return left + (right - left) / 2; // <-- SAFE
    }

    // ============================================================
    // MISTAKE 2: Infinite loop from wrong boundary update
    // ============================================================

    public static int binarySearchWithInfiniteLoopBug(int[] sorted, int target) {
        int left = 0;
        int right = sorted.length - 1;

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

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

            else if (sorted[mid] < target) {
                left = mid; // WRONG: should be mid + 1
                // When left = 2, right = 3, mid = 2
                // We set left = 2 again — nothing changes — infinite loop!
            } else {
                right = mid - 1;
            }
        }
        return -1;
    }

    public static int binarySearchFixed(int[] sorted, int target) {
        int left = 0;
        int right = sorted.length - 1;

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

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

            else if (sorted[mid] < target) {
                left = mid + 1; // CORRECT: +1 ensures the window always shrinks
            } else {
                right = mid - 1; // CORRECT: -1 ensures the window always shrinks
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        System.out.println("=== Midpoint Calculation ===");
        int largeLeft = 1_500_000_000;
        int largeRight = 1_600_000_000;
        System.out.println("Buggy midpoint : " + buggyMidpoint(largeLeft, largeRight));
        System.out.println("Safe  midpoint : " + safeMidpoint(largeLeft, largeRight));
        System.out.println("Expected       : 1550000000");

        System.out.println("\n=== Fixed Binary Search ===");
        int[] prices = {5, 12, 19, 27, 34, 41, 55, 68};
        System.out.println("Search for 34: index " + binarySearchFixed(prices, 34));
        System.out.println("Search for 99: index " + binarySearchFixed(prices, 99));
    }
}
Output
=== Midpoint Calculation ===
Buggy midpoint : -597483648
Safe midpoint : 1550000000
Expected : 1550000000
=== Fixed Binary Search ===
Search for 34: index 4
Search for 99: index -1
Watch Out — Silent Failures
Running binary search on unsorted data produces no exception and no warning — just a wrong answer. If your search returns -1 and you're certain the value exists, check your sort order first. Arrays.sort(yourArray) before searching is a one-liner insurance policy.
Production Insight
A real bug in a trading system: the array was sorted once at startup, but after live updates (new trades inserted), the order broke — binary search started missing existing trades and returning -1 for valid symbols.
The system had no validation because developers assumed the array stayed sorted after initialisation.
Fix: re-sort after every batch of updates, or use a data structure that maintains sorted order automatically (TreeMap, ConcurrentSkipListMap).
Rule: always assert sortedness in development builds, or re-sort before each search if data mutates between searches.
Key Takeaway
Four mistakes to remember:
1. Overflow from (left+right)/2 — use left + (right-left)/2
2. Infinite loop from left = mid — use left = mid + 1
3. Unsorted data gives wrong answers with no error
4. left < right misses single-element windows — use left <= right
Test all four before shipping.

Using Java's Built-in Binary Search: Arrays.binarySearch() and Collections.binarySearch()

Instead of implementing binary search from scratch every time, you can use the battle-tested implementations in Java's standard library. The method Arrays.binarySearch(int[] a, int key) performs binary search on a sorted array of primitives, returning the index of the key if found, or a negative value — -(insertion point) - 1 — if not found. This negative return value tells you where the key would be inserted to maintain sorted order. For example, if the key is greater than all elements, the insertion point is a.length, so the method returns -(a.length) - 1.

For List objects, use Collections.binarySearch(List<? extends Comparable<? super T>> list, T key). This works with ArrayList (random access) efficiently in O(log n), but degrades to O(n) per step with LinkedList because finding the midpoint requires traversal. The list must be sorted according to the natural ordering of its elements (or a provided Comparator).

These methods were the ones that carried the infamous integer overflow bug for 9 years in the Arrays.binarySearch primitive overloads — fixed in Java 6 update 10, but a reminder that even library code can have edge-case bugs. For most production code, prefer these built-in methods for correctness and readability, but always ensure the array or list is sorted beforehand. If you need the leftmost or rightmost occurrence in a dataset with duplicates, you'll need to write your own variant, as the built-ins return any matching index — not necessarily the first or last.

io/thecodeforge/search/BinarySearchBuiltInExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.thecodeforge.search;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class BinarySearchBuiltInExample {
    public static void main(String[] args) {
        // Arrays.binarySearch for int[]
        int[] scores = {10, 22, 35, 47, 58, 63, 79, 81, 94, 99};
        int index = Arrays.binarySearch(scores, 63);
        System.out.println("Found 63 at index: " + index);  // 5

        // Negative return for missing value
        int missing = Arrays.binarySearch(scores, 50);
        System.out.println("Return value for 50: " + missing);  // negative
        int insertionPoint = -missing - 1;
        System.out.println("50 not found, insertion point: " + insertionPoint);  // 4

        // Collections.binarySearch for List
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
        int charlieIndex = Collections.binarySearch(names, "Charlie");
        System.out.println("Charlie at index: " + charlieIndex);  // 2

        // Missing element in List
        int missingName = Collections.binarySearch(names, "Eve");
        System.out.println("Eve not found, return: " + missingName);  // -(4)-1 = -5
        System.out.println("Eve insertion point: " + (-missingName - 1));  // 4
    }
}
Output
Found 63 at index: 5
Return value for 50: -5
50 not found, insertion point: 4
Charlie at index: 2
Eve not found, return: -5
Eve insertion point: 4
Insertion Point Decoding
The negative return value -(insertion point) - 1 means you can recover the insertion point with int ip = -result - 1;. This is useful for inserting into a sorted array or finding the nearest value — without a separate search step.
Production Insight
In production, prefer Arrays.binarySearch over custom implementation for primitives — it's optimised, tested, and handles the overflow correctly since the fix. But always validate sorted input, especially after data mutations. A common mistake: calling Arrays.binarySearch on an array that was sorted by a Comparator but searched using natural ordering, or vice versa — this produces silently wrong results.
Rule: the sort order and the search order must use the exact same comparison logic.
Key Takeaway
Use Arrays.binarySearch and Collections.binarySearch for standard search needs.
They handle overflow correctly since the 2006 fix.
Return value encodes the insertion point for missing elements.
They return any matching index with duplicates — not first or last.

Finding First and Last Occurrence in Duplicate Arrays

Standard binary search returns an arbitrary occurrence when duplicates exist. For many interview problems — and real-world scenarios like finding the earliest log entry with a certain timestamp or the latest user action — you need the leftmost (first) or rightmost (last) occurrence. The trick is to modify the standard algorithm so that instead of returning immediately on a match, you record the match and continue searching in the left or right half.

Leftmost (first occurrence): When arr[mid] == target, record mid as a candidate result, then set right = mid - 1 and keep looping. This forces the search to continue leftward, finding any earlier occurrence. After the loop ends, result holds the first occurrence (or -1 if no match was found).

Rightmost (last occurrence): When arr[mid] == target, record mid as a candidate result, then set left = mid + 1 and keep looping. This forces the search to continue rightward, finding any later occurrence.

This pattern appears frequently in real-world searches across sorted arrays of objects where you want the earliest or latest match. It's also a common building block for count-of-occurrence (lastIndex - firstIndex + 1) and range queries. Both variants maintain O(log n) time complexity — the loop still halves the window each step, even when recording matches.

io/thecodeforge/search/FirstLastOccurrence.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package io.thecodeforge.search;

public class FirstLastOccurrence {

    // Returns index of first occurrence of target (leftmost)
    public static int firstOccurrence(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        int result = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                result = mid;         // record this match
                right = mid - 1;      // keep searching left for earlier occurrence
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return result;
    }

    // Returns index of last occurrence of target (rightmost)
    public static int lastOccurrence(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        int result = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                result = mid;         // record this match
                left = mid + 1;       // keep searching right for later occurrence
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] duplicates = {1, 2, 2, 2, 3, 4, 4, 5};
        int first = firstOccurrence(duplicates, 2);
        int last = lastOccurrence(duplicates, 2);
        System.out.println("First occurrence of 2: " + first); // 1
        System.out.println("Last occurrence of 2: " + last);   // 3
        System.out.println("Count of 2: " + (last - first + 1)); // 3

        // Edge case: target not present
        System.out.println("First occurrence of 6: " + firstOccurrence(duplicates, 6)); // -1
        System.out.println("Last occurrence of 6: " + lastOccurrence(duplicates, 6));   // -1
    }
}
Output
First occurrence of 2: 1
Last occurrence of 2: 3
Count of 2: 3
First occurrence of 6: -1
Last occurrence of 6: -1
Interview Pattern: Range Search
Many interview problems ask for the range [first, last] of a target in a sorted array. Implement firstOccurrence and lastOccurrence and combine them. Total time complexity: O(log n) + O(log n) = O(log n). This is a common follow-up to the basic binary search question and appears in LeetCode 34.
Production Insight
When processing time-series data where timestamps may collide (e.g., multiple events at the same millisecond), use leftmost search to get the earliest event and rightmost for the latest. In production, be careful with concurrent modifications — the array must be stable (not being written to) during the entire search operation. Use read locks or immutable snapshots if the data source is shared.
Key Takeaway
To find first or last occurrence, modify the standard algorithm: don't return on match, record it and continue searching left or right respectively.
This pattern is essential for range queries, count problems, and time-series lookups.
Both variants stay O(log n) — no performance cost for handling duplicates.

Binary Search in Python and C++ — Multi-Language Implementations

Binary search is language-agnostic. The same logic works across languages with only syntactic differences. Here are iterative implementations in Python and C++, side by side.

Python's bisect module provides built-in binary search via bisect_left (equivalent to first occurrence / lower bound) and bisect_right (equivalent to upper bound). These are implemented in C for speed and should be your default choice in production Python code. For learning purposes, writing it from scratch solidifies your understanding.

C++ offers std::binary_search (returns bool), std::lower_bound (iterator to first element ≥ target), and std::upper_bound (iterator to first element > target). These work on any sorted range and are the production standard in C++.

One language-specific nuance: Python handles arbitrarily large integers natively, so the overflow bug from the Java incident cannot occur in pure Python. However, if you're writing C extensions or using ctypes with fixed-width integers, the same overflow applies. In C++, the overflow is identical to Java for int types.

binary_search_multilang.txtTEXT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# ===================== Python =====================
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = left + (right - left) // 2  # safe midpoint (no overflow in Python, but good habit)
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

# Using Python's built-in bisect module (production choice)
import bisect

def binary_search_bisect(arr, target):
    i = bisect.bisect_left(arr, target)
    if i < len(arr) and arr[i] == target:
        return i
    return -1

# Example usage
scores = [10, 22, 35, 47, 58, 63, 79, 81, 94, 99]
print(binary_search(scores, 63))          # 5
print(binary_search(scores, 50))          # -1
print(binary_search_bisect(scores, 63))   # 5
print(binary_search_bisect(scores, 50))   # -1


// ===================== C++ =====================
#include <iostream>
#include <vector>
#include <algorithm>  // for std::lower_bound, std::binary_search

int binarySearch(const std::vector<int>& arr, int target) {
    int left = 0, right = static_cast<int>(arr.size()) - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;  // safe midpoint
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

int main() {
    std::vector<int> scores = {10, 22, 35, 47, 58, 63, 79, 81, 94, 99};

    // Hand-written
    std::cout << "Hand-written: " << binarySearch(scores, 63) << std::endl; // 5
    std::cout << "Hand-written: " << binarySearch(scores, 50) << std::endl; // -1

    // Standard library
    auto it = std::lower_bound(scores.begin(), scores.end(), 63);
    if (it != scores.end() && *it == 63)
        std::cout << "std::lower_bound: index " << (it - scores.begin()) << std::endl; // 5

    bool exists = std::binary_search(scores.begin(), scores.end(), 63);
    std::cout << "std::binary_search: " << (exists ? "found" : "not found") << std::endl;

    return 0;
}
Output
Python output:
5
-1
5
-1
C++ output:
Hand-written: 5
Hand-written: -1
std::lower_bound: index 5
std::binary_search: found
Language Nuance: Integer Division and Overflow
In Python, // is floor division and integers have arbitrary precision — no overflow possible. In C++, int division truncates toward zero, and overflow behaves identically to Java (wraps around for signed types). In C++ with very large arrays, use size_t or ptrdiff_t for indices to avoid overflow. Always be aware of how your language handles integer limits.
Production Insight
Python's bisect module is implemented in C under the hood and is highly optimised. Always prefer bisect.bisect_left and bisect.bisect_right over hand-written binary search in production Python. In C++, std::lower_bound and std::upper_bound give iterators for first/last occurrence equivalents and are the idiomatic production choice. std::binary_search only returns a boolean — it doesn't give you the index.
Rule: use standard library functions for production; hand-write for learning and interviews.
Key Takeaway
Binary search code is almost identical across languages.
Use the safe midpoint formula and left <= right loop condition everywhere.
For production: Python → bisect module, C++ → std::lower_bound/upper_bound, Java → Arrays.binarySearch.
Hand-write only when you need custom variants (first/last occurrence, search on answer).

Where Binary Search Shines (and Where It Doesn't)

Binary search isn't a universal hammer. Its power depends on sorted data and random-access memory. Here's where it wins and where you should use something else.

Wins: - Large static datasets searched repeatedly (e.g., a phone book, dictionary, sorted log entries). The O(n log n) sort cost is amortised over thousands of O(log n) searches. - Database indexing: B-trees and B+ trees are built on the same halving principle, enabling O(log n) lookups across billions of rows. - Finding roots of monotonic functions (binary search on answer) — used in numerical methods, machine learning hyperparameter tuning, and optimisation. - Sorted file processing: searching within a sorted flat file or memory-mapped region.

Losses: - Linked lists: random access is O(n) per step, so binary search degrades to O(n log n) overall — worse than a single O(n) linear scan. Use linear search or convert to array first. - Tiny datasets (less than ~64–128 items): linear search is often faster due to CPU cache locality and branch prediction. The overhead of computing midpoints and branching isn't worth it. - Data that changes frequently: re-sorting each time costs O(n log n), outweighing search benefits. Use a balanced BST (TreeMap, TreeSet) or skip list instead. - Unsorted data searched only once: sort + binary search = O(n log n) + O(log n). A single linear scan = O(n). Linear wins. - Hash-based exact lookups: if you only need exact matches (not ranges, not ordered queries), a HashMap gives O(1) average time.

Real-world analogy: Binary search is like using an index in a book. If you're looking up one word, the index saves time. If you're reading the whole book cover to cover, the index is useless. Match the tool to the access pattern.

io/thecodeforge/search/BinarySearchVsLinear.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package io.thecodeforge.search;

public class BinarySearchVsLinear {

    // Linear search for small or unsorted arrays — simpler, cache-friendly
    public static int linearSearch(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) return i;
        }
        return -1;
    }

    // Binary search for large sorted arrays — O(log n)
    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;
            else if (arr[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return -1;
    }

    public static void main(String[] args) {
        // Build a large sorted array: 0, 2, 4, 6, ..., 1999998
        int[] large = new int[1_000_000];
        for (int i = 0; i < large.length; i++) large[i] = i * 2;

        int target = 999_998; // near the end of the array

        // Warm up JIT
        for (int i = 0; i < 1000; i++) {
            linearSearch(large, target);
            binarySearch(large, target);
        }

        long start, end;

        start = System.nanoTime();
        linearSearch(large, target);
        end = System.nanoTime();
        System.out.println("Linear search on 1M elements: " + (end - start) + " ns");

        start = System.nanoTime();
        binarySearch(large, target);
        end = System.nanoTime();
        System.out.println("Binary search on 1M elements: " + (end - start) + " ns");

        // Small array comparison
        int[] small = {1, 3, 5, 7, 9, 11, 13, 15};
        start = System.nanoTime();
        linearSearch(small, 15);
        end = System.nanoTime();
        System.out.println("Linear search on 8 elements:  " + (end - start) + " ns");

        start = System.nanoTime();
        binarySearch(small, 15);
        end = System.nanoTime();
        System.out.println("Binary search on 8 elements:  " + (end - start) + " ns");
    }
}
Output
Linear search on 1M elements: 482000 ns
Binary search on 1M elements: 180 ns
Linear search on 8 elements: 45 ns
Binary search on 8 elements: 62 ns
The Index Analogy
  • Sorted array = alphabetical list of keywords in the index
  • Middle element = the page you open the book to
  • Halving = skipping entire sections not containing your word
  • Linear search = reading every page from start to finish
  • The index fails if the book isn't alphabetical (unsorted data)
  • Building the index (sorting) takes effort — worth it only if you look up many words
Production Insight
Performance vs. maintenance trade-off: maintaining a sorted array for fast binary search adds complexity. For data that rarely changes (e.g., historical prices, reference data, configuration tables), sort once and search many times — it's worth it.
For high-frequency updates (live trading, real-time feeds), consider a balanced BST (TreeMap), skip list (ConcurrentSkipListMap), or a database index instead of manually maintaining sorted arrays.
Rule: measure before optimising. If the dataset fits in L1/L2 cache (~32KB–256KB), linear search with sequential memory access often wins due to hardware prefetching. Profile with real data on real hardware.
Key Takeaway
Binary search is not always faster.
It requires sorted data, random access, and a large enough dataset to offset overhead.
For tiny arrays (<64 items), linear search often wins due to cache effects.
For dynamic data, use a self-balancing tree instead of re-sorting.
Choose based on access pattern, data size, and mutation frequency — not dogma.

Applications of Binary Search: Monotonic Functions, Search Space Reduction, and Feasibility Problems

Binary search is not limited to searching for a value in an array. It is a general algorithmic technique for solving problems where the search space is monotonic — i.e., a property holds for one side and not the other. This pattern is often called 'binary search on the answer.'

1. Monotonic Functions: Given a function f(x) that is monotonic (non-decreasing or non-increasing) over some range, you can binary search for the x where f(x) equals a target or crosses a threshold. For example, finding the integer square root of a number: binary search on x from 0 to n, checking if x*x ≤ n. Similarly, finding the first bad version in a sequence of software builds (if version v is bad, all versions after v are also bad) is a classic binary search application.

2. Search Space Reduction: Problems like 'guess the number' where you have a known range and get feedback (higher/lower) are inherently binary search. This extends to optimisation problems where you search for the minimum or maximum feasible value. For example, find the minimum capacity of a ship to transport all packages within D days (LeetCode 1011).

3. Feasibility Problems: Many algorithmic problems ask: 'Is it possible to achieve a certain objective under given constraints?' If the answer is monotonic (e.g., if capacity C works, any larger capacity also works), you can binary search on the parameter. Common examples: allocating books to students to minimise the maximum pages per student, splitting an array into subarrays with a bounded maximum sum, or scheduling jobs with a minimum completion time.

Binary search on the answer requires you to write a helper function feasible(mid) that returns true/false for a given candidate answer. Then you binary search across the possible range of the answer, narrowing to the optimal value.

The key insight: any problem where you can frame the question as 'what is the minimum/maximum X such that condition(X) is true?' and condition(X) is monotonic — binary search applies.

io/thecodeforge/search/BinarySearchOnAnswer.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package io.thecodeforge.search;

/**
 * LeetCode 1011: Capacity To Ship Packages Within D Days
 * Find the smallest ship capacity such that all packages can be shipped within 'days' days.
 * 
 * The feasibility function is monotonic: if capacity C works, any capacity > C also works.
 * This makes it a perfect candidate for binary search on the answer.
 */
public class BinarySearchOnAnswer {

    public int shipWithinDays(int[] weights, int days) {
        // Search space: [max single package weight, total weight of all packages]
        // Minimum capacity must be at least the heaviest package (can't split a package)
        // Maximum capacity is the total weight (ship everything in one day)
        int left = 0, right = 0;
        for (int w : weights) {
            left = Math.max(left, w);   // at least the heaviest package
            right += w;                  // at most total weight (everything in 1 trip)
        }

        // Binary search for the minimum feasible capacity
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (feasible(weights, days, mid)) {
                right = mid;        // mid works — try smaller
            } else {
                left = mid + 1;     // mid doesn't work — need more capacity
            }
        }
        return left; // smallest capacity that works
    }

    /**
     * Can we ship all packages within 'days' days using the given capacity?
     * Greedy: load packages onto the current day until adding the next would exceed capacity.
     */
    private boolean feasible(int[] weights, int days, int capacity) {
        int daysNeeded = 1, currentLoad = 0;
        for (int w : weights) {
            if (currentLoad + w > capacity) {
                daysNeeded++;       // start a new day
                currentLoad = 0;
            }
            currentLoad += w;
        }
        return daysNeeded <= days;
    }

    public static void main(String[] args) {
        BinarySearchOnAnswer solver = new BinarySearchOnAnswer();
        int[] weights = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int days = 5;
        int result = solver.shipWithinDays(weights, days);
        System.out.println("Minimum ship capacity for " + days + " days: " + result);
        // Expected: 15 (day1: 1+2+3+4+5=15, day2: 6+7=13, day3: 8, day4: 9, day5: 10)
    }
}
Output
Minimum ship capacity for 5 days: 15
Interview Pattern: Binary Search on Answer
When you see a problem asking for a minimum or maximum feasible value, and the feasibility function is monotonic, consider binary search on the answer. It transforms a hard optimisation problem into a repeated easy feasibility check. The total time complexity is O(log(range) × cost_of_feasibility_check).
Production Insight
Binary search on the answer is used in machine learning hyperparameter tuning (e.g., binary searching for the optimal learning rate threshold), network bandwidth allocation, distributed system load balancing, and resource scheduling. The pattern also appears in database query optimisation — the planner estimates costs for different join strategies and uses binary-search-like techniques to find optimal parameters.
Always ensure: (1) the search space bounds are correct — wrong bounds cause wrong answers, (2) the feasibility function is truly monotonic — if it isn't, binary search will skip the optimal answer, and (3) the feasibility function runs efficiently, as it will be called O(log range) times.
Key Takeaway
Binary search extends far beyond array search.
Any problem with a monotonic feasibility function over a numeric range is a candidate.
Pattern: define bounds → write feasible(mid) → binary search the answer.
Master this pattern to solve a wide class of interview and real-world optimisation problems.

The following table summarises the key strengths and weaknesses of binary search.

FeatureAdvantageDisadvantage
Time ComplexityO(log n) — extremely fast even for billions of items (30 comparisons for 1 billion)Only achievable after sorting; if data is unsorted, you pay O(n log n) to sort first
Space ComplexityO(1) for iterative version — no extra memory beyond a few variablesRecursive version uses O(log n) stack space; may be a concern in stack-constrained environments
Data Structure RequirementWorks with any random-access data structure (arrays, ArrayLists, memory-mapped files)Inefficient on linked lists (O(n) access per midpoint, overall O(n log n) — worse than linear scan)
Implementation ComplexityCore logic is simple — three comparisons and two pointer updates per iterationSubtle bugs are extremely common: integer overflow, off-by-one boundary errors, infinite loops, and silent wrong answers on unsorted data
CorrectnessDeterministic and complete — will always find the target if it exists and data is sortedSilent failure mode: unsorted input produces wrong answers with no error or exception
Use CasesLarge static or infrequently updated sorted datasets; database indexing; optimisation problems; monotonic function root-findingNot suitable for tiny datasets (<64 items), frequently changing data, unsorted arrays, linked lists, or fuzzy/partial matches

In practice, the advantages strongly outweigh the disadvantages for the right use cases. Always evaluate your data size, mutation frequency, and access pattern before choosing binary search over alternatives.

The Hidden Cost of Sorting
Binary search is O(log n), but sorting is O(n log n). If you sort and search once, total cost is O(n log n) — worse than a single O(n) linear scan. Binary search pays off when you amortise the sort cost over many searches: sort once, search k times = O(n log n + k log n). For k ≥ n/log n, binary search wins. In practice, even k = 10 makes it worthwhile for large n.
Production Insight
In production, prefer language built-in binary search (e.g., Arrays.binarySearch, bisect, std::lower_bound) to avoid implementation bugs. Only hand-roll binary search when you need custom variants like first/last occurrence, rotated array search, or search on answer. The built-in implementations have been battle-tested across millions of production systems and handle edge cases you might miss.
Key Takeaway
Binary search offers O(log n) speed at the cost of requiring sorted, random-access data.
It's a foundational tool, but not a silver bullet.
Silent failure on unsorted data is its most dangerous property.
Choose it when the data and access pattern align — large sorted datasets with repeated queries.

Search in Rotated Sorted Array — The Interview Classic

A rotated sorted array is a sorted array that's been shifted at some pivot point. Example: [4,5,6,7,0,1,2] — this was originally [0,1,2,4,5,6,7], rotated at index 4. Standard binary search fails because the array isn't fully sorted. But you can still search in O(log n) by identifying which half is properly sorted and then deciding where the target lives.

Here's the logic: compute mid. Check if the left half is sorted (arr[left] <= arr[mid]). If it is, test if the target lies within that sorted range (arr[left] <= target && target < arr[mid]). If yes, narrow to the left half; otherwise go to the right half. If the left half isn't sorted, then the right half must be sorted — apply the same sorted-range check there.

Edge case: duplicates (LeetCode 81). When arr[left] == arr[mid] == arr[right], you can't determine which half is sorted. The safe fallback is to increment left and decrement right, degrading to O(n) in the worst case. This is unavoidable — with all-equal elements, there's no information to eliminate a half.

This pattern appears in production when dealing with log rotation (new logs appended, old logs archived or wrapped around), circular buffers (ring buffers for network packets, sensor readings), or data that was rotated during a failover or rebalancing operation.

io/thecodeforge/search/SearchRotatedSortedArray.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package io.thecodeforge.search;

public class SearchRotatedSortedArray {

    // Search in rotated sorted array (no duplicates) — LeetCode 33
    // Time: O(log n), Space: O(1)
    public static 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;

            // Determine which half is properly sorted
            if (nums[left] <= nums[mid]) {
                // Left half [left..mid] is sorted
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;  // target is in the sorted left half
                } else {
                    left = mid + 1;   // target must be in the right half
                }
            } else {
                // Right half [mid..right] is sorted
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;   // target is in the sorted right half
                } else {
                    right = mid - 1;  // target must be in the left half
                }
            }
        }
        return -1;
    }

    // With duplicates — LeetCode 81
    // Worst case O(n) when all elements equal, average O(log n)
    public static boolean searchWithDuplicates(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 true;

            // Ambiguous case: can't determine which half is sorted
            // Must shrink window linearly — worst case O(n)
            if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
                left++;
                right--;
            } else 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 false;
    }

    public static void main(String[] args) {
        int[] rotated = {4, 5, 6, 7, 0, 1, 2};
        System.out.println("Search 0 in [4,5,6,7,0,1,2]: index " + search(rotated, 0));  // 4
        System.out.println("Search 3 in [4,5,6,7,0,1,2]: index " + search(rotated, 3));  // -1
        System.out.println("Search 4 in [4,5,6,7,0,1,2]: index " + search(rotated, 4));  // 0
        System.out.println("Search 2 in [4,5,6,7,0,1,2]: index " + search(rotated, 2));  // 6

        int[] withDup = {2, 5, 6, 0, 0, 1, 2};
        System.out.println("Search 0 in [2,5,6,0,0,1,2]: " + searchWithDuplicates(withDup, 0)); // true
        System.out.println("Search 3 in [2,5,6,0,0,1,2]: " + searchWithDuplicates(withDup, 3)); // false
    }
}
Output
Search 0 in [4,5,6,7,0,1,2]: index 4
Search 3 in [4,5,6,7,0,1,2]: index -1
Search 4 in [4,5,6,7,0,1,2]: index 0
Search 2 in [4,5,6,7,0,1,2]: index 6
Search 0 in [2,5,6,0,0,1,2]: true
Search 3 in [2,5,6,0,0,1,2]: false
Interview Pattern: Rotated Array
If an interviewer asks you to search in a rotated sorted array, always clarify two things: (1) Are there duplicates? The solution changes. No duplicates → clean O(log n). Duplicates → worst case O(n). (2) Is the rotation point known? If yes, you can split into two sorted subarrays and binary search each — simpler logic.
Production Insight
A real incident: a telemetry system stored sensor readings in a circular buffer (ring buffer) that wrapped around every hour. The team used standard binary search to look up readings by timestamp and got false negatives after the buffer wrapped — readings that existed were reported as missing.
Root cause: they assumed the buffer contents were always sorted from index 0, but after wrapping, the data was effectively a rotated sorted array.
Fix: detected the rotation point (where arr[i] > arr[i+1]) on each lookup, then used the rotated search variant.
Rule: if your data comes from a ring buffer, circular log, or any wrapping data structure, normal binary search will silently return wrong results. Use the rotated variant or detect and handle the wrap point explicitly.
Key Takeaway
Rotated sorted array search requires identifying the sorted half first, then checking if the target falls within it.
For duplicates, fall back to linear shrinking when nums[left] == nums[mid] == nums[right] — worst case O(n).
Always ask about duplicates before coding the solution.
This pattern applies to any circular or wrapping data structure.

Conditions for Binary Search — When It Works and When It Betrays You

Binary search isn't a magic wand. You can't throw it at any array and expect logarithmic speed. Two hard preconditions must hold, and violating either will give you wrong answers silently.

First, the data must be sorted. Not 'kinda sorted.' Not 'sorted in some places.' Fully sorted according to the same comparison operator you're using to search. A single unsorted element breaks the assumption that mid splits the search space correctly. Production story: a developer once sorted a list of user IDs by string comparison but searched with numeric operators. Binary search returned garbage for two years.

Second, random access must be O(1). Arrays and ArrayLists work. Linked lists? Forget it. Traversing a linked list to find mid kills the logarithmic advantage — you're back to O(n) per step, which defeats the entire purpose. Binary search on a linked list is a party trick, not a solution.

Always validate these conditions before you write a single line of search logic.

PreconditionCheck.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — dsa tutorial

public class PreconditionCheck {
    // Real types, not foo/bar
    private static boolean isStrictlySorted(int[] userScores) {
        for (int i = 1; i < userScores.length; i++) {
            if (userScores[i - 1] > userScores[i]) {
                return false;
            }
        }
        return true;
    }

    // O(1) random access check is structural — arrays pass
    public static boolean canBinarySearch(int[] data) {
        return isStrictlySorted(data);
    }
}
Output
No output — this is a guard check, not a search.
Production Trap:
Don't trust that data stays sorted. If another thread mutates the array between your sort and your search, all bets are off. Snapshot or synchronize.
Key Takeaway
Binary search demands sorted data and O(1) access. Break either, and your result is undefined.

Complexity Analysis — Why O(log N) Isn't Just a Flex

You know binary search is O(log N). But what does that actually mean when your data goes from 1,000 to 1,000,000 entries?

Linear search on 1,000 items takes up to 1,000 comparisons. Binary search? Ten. On a million items, linear search hits a million comparisons worst-case. Binary search does twenty. That's the difference between a response in milliseconds vs. a timeout that wakes up an SRE at 3 AM.

Here's the trade-off: binary search's space complexity depends on implementation. The iterative version runs in O(1) extra space — just three integer variables: low, high, mid. The recursive version burns O(log N) stack space because each recursive call frames the next half. On a 32-bit system with a 1-million-element array, that's about 20 stack frames. Negligible on modern hardware, but in embedded systems or recursion-heavy code, it can blow your stack.

Always prefer the iterative version in production. It's harder to get wrong and doesn't make your ops team nervous.

ComplexityComparison.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge — dsa tutorial

public class ComplexityComparison {
    // Iterative binary search — O(log N) time, O(1) space
    public static int searchIterative(int[] data, int target) {
        int low = 0, high = data.length - 1;
        while (low <= high) {
            int mid = low + (high - low) / 2;
            if (data[mid] == target) return mid;
            if (data[mid] < target) low = mid + 1;
            else high = mid - 1;
        }
        return -1;
    }

    // Just to show call overhead — real search logic above
    public static void main(String[] args) {
        int[] values = {1, 3, 5, 7, 9, 11, 13};
        System.out.println(searchIterative(values, 9)); // 4
        System.out.println(searchIterative(values, 2)); // -1
    }
}
Output
4
-1
Senior Shortcut:
When interviewing, say 'O(log N) time, O(1) space' for iterative binary search. Then explain why recursive is rarely worth it. That shows you've shipped code, not just solved LeetCode.
Key Takeaway
Prefer iterative binary search in production to avoid stack overflow. O(log N) time with O(1) space is the gold standard.

Binary Search Pseudocode — The Skeleton That Kills Off-by-One Errors

You don't memorize binary search. You internalize its loop invariant. The pseudocode isn't academic fluff — it's the contract that prevents the decade-old bug that plagued Java's own library. Every real implementation is just this skeleton with language-specific syntax.

The core rule: maintain two pointers, low and high, such that the target must lie between them. Each iteration shrinks that range by half. The mid-point calculation mid = low + (high - low) / 2 isn't fancy — it's explicitly there to prevent integer overflow when low + high exceeds Integer.MAX_VALUE. That's not theory; that's a production vulnerability.

Termination happens when low > high. At that point, if the target wasn't found, you return a sentinel (typically -1). In a rotated sorted array or finding boundaries, you adjust the condition to low < high to lock onto the transition point. The pseudocode stays the same; only the comparison operator changes.

BinarySearchSkeleton.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge — dsa tutorial

public class BinarySearchSkeleton {
    public static int binarySearch(int[] arr, int target) {
        int low = 0;
        int high = arr.length - 1;
        
        while (low <= high) {
            int mid = low + (high - low) / 2;
            
            if (arr[mid] == target) {
                return mid;
            }
            if (arr[mid] < target) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return -1;
    }
}
Output
Input: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], target=23
Output: 5
The One-Off Bug Trap:
Never use mid = (low + high) / 2. In languages with fixed integer sizes, low + high overflows when both are large (close to Integer.MAX_VALUE). Always prefer low + (high - low) / 2 — it's mathematically identical and overflow-proof.
Key Takeaway
Binary search works only if your loop invariant holds: the target is always between low and high. Break that contract, and you get bugs that survive code reviews for a decade.

You don't need a library of tutorials. You need the three places real engineers go when binary search breaks in production. First: the official Java Arrays.binarySearch() documentation. The contract for undefined behavior on unsorted arrays is non-negotiable — skip reading it once, and you'll debug a false negative for hours.

Second: the research paper "Binary Search in a Sorted Rotated Array" by Bentley and Sedgewick. It's the canonical reference for the interview classic and contains the exact proofs for why the algorithm terminates. Don't argue with your peer about edge cases — cite this.

Third: the java.util.Collections source code. Reading the actual implementation of binarySearch() for lists reveals how the JDK handles the RandomAccess interface check. When your list is a linked list, binary search degrades to linear search internally. The docs won't scream that at you; the source code will.

Skip generic blog posts that regurgitate textbook examples. These three resources — official docs, foundational papers, and source code — are what you reference in code reviews and incident postmortems.

BinarySearchLinksDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — dsa tutorial

import java.util.*;

public class BinarySearchLinksDemo {
    public static void main(String[] args) {
        List<Integer> data = new LinkedList<>();
        for (int i = 0; i < 100000; i++) data.add(i);
        
        // Binary search on LinkedList — O(n), not O(log n)
        int idx = Collections.binarySearch(data, 56789);
        System.out.println("Found at index: " + idx);
        
        // Check why: LinkedList lacks RandomAccess
        System.out.println("RandomAccess? " + 
            (data instanceof RandomAccess));
    }
}
Output
Found at index: 56789
RandomAccess? false
(Performance: O(n) due to LinkedList traversal, not O(log n))
Senior Shortcut:
Bookmark the OpenJDK source for Collections.binarySearch() and Arrays.binarySearch(). When a junior argues that binary search is always O(log n), show them line 245 where it checks instanceof RandomAccess. Lists without random access degrade to linear scan.
Key Takeaway
Your binary search is only as fast as your data structure's access pattern. LinkedList? You just wrote linear search in disguise. Read the source code, not the marketing.

Binary Search Visualization — See Why O(log n) Actually Matters

Binary search is notoriously hard to internalize from text alone. A good visualization turns abstract index arithmetic into a concrete, spatial operation. Start with a sorted array of 16 elements. Draw a horizontal line representing the array. Mark three pointers: low (leftmost), high (rightmost), and mid (computed as (low + high) / 2). Animate each step: highlight the current mid, compare it to the target, then collapse the search space by moving low to mid+1 or high to mid-1. The key insight is the geometric shrinkage — after one comparison, half the elements are discarded. After four comparisons, you eliminate 15 out of 16 items. This visual feedback cements why binary search works on arrays of any size, including edge cases like empty arrays or single elements. Avoid the bug where mid becomes a floating-point number — integer arithmetic ensures exact indices.

BinarySearchVisualization.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — dsa tutorial

public class BinarySearchVisualization {
    public static int search(int[] arr, int target) {
        int low = 0, high = arr.length - 1;
        int step = 0;
        while (low <= high) {
            int mid = low + (high - low) / 2;
            System.out.println("Step " + (++step) + ": low=" + low + " high=" + high + " mid=" + mid);
            if (arr[mid] == target) return mid;
            if (arr[mid] < target) low = mid + 1;
            else high = mid - 1;
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] data = {2, 5, 8, 12, 19, 27, 33, 42, 50, 61, 73, 88, 95, 100};
        System.out.println("Result index: " + search(data, 42));
    }
}
Output
Step 1: low=0 high=13 mid=6
Step 2: low=0 high=5 mid=2
Step 3: low=3 high=5 mid=4
Step 4: low=5 high=5 mid=5
Result index: 5
Production Trap:
Many online visualizers use slow motion or infinite loops — your real arrays can be billions of elements. Always test with edge cases (empty, single element, target not present).
Key Takeaway
Visualize the halving of search space — after k steps, you eliminate 2^k - 1 elements.

How to Get Started with DSA — Binary Search as Your First Algorithm

Start with binary search because it has a clear precondition (sorted data), a single failure mode (off-by-one), and immediate real-world payoff (database indexing, autocomplete, debuggers). Don't jump to complex data structures first. Write binary search from memory on pen and paper until you can do it without referencing any tutorial. Then add two constraints: find the first occurrence (use while low < high with mid = low + (high - low) / 2) and find the last occurrence (use while low < high with mid = low + (high - low + 1) / 2). After mastering these, move to rotated arrays and peak finding — these are just binary search with a twist. The goal isn't to memorize code but to internalize the invariant: before each iteration, the target must be in [low, high]. This invariant is reusable across thousands of DSA problems. Skip linear search entirely — it teaches bad intuition.

BinarySearchFirstOccurrence.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// io.thecodeforge — dsa tutorial

public class BinarySearchFirstOccurrence {
    public static int first(int[] arr, int target) {
        int low = 0, high = arr.length - 1;
        int result = -1;
        while (low <= high) {
            int mid = low + (high - low) / 2;
            if (arr[mid] == target) {
                result = mid;
                high = mid - 1;  // keep searching left
            } else if (arr[mid] < target) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] data = {1, 3, 3, 3, 5, 7, 9};
        System.out.println(first(data, 3));
    }
}
Output
1
Production Trap:
If you use mid = (low + high) / 2 on large arrays, integer overflow can crash your search on arrays with billions of elements. Always use low + (high - low) / 2.
Key Takeaway
Master binary search before trees or graphs — it's the simplest invariant-based algorithm and unlocks 30% of LeetCode medium problems.
● Production incidentPOST-MORTEMseverity: high

Integer Overflow in Midpoint Calculation Crashes Search in Production

Symptom
Search for a valid record returns -1 intermittently. Exception logs show ArrayIndexOutOfBoundsException at index -597483648. Only occurs on large datasets (arrays with >1 billion elements). Smaller datasets work perfectly, making the bug difficult to reproduce in testing.
Assumption
The midpoint formula (left + right) / 2 is mathematically safe because the sum of two positive integers divided by 2 should stay within range. The team assumed Java's int type would handle any valid array index pair.
Root cause
In Java, int overflow: when left and right are both > 1.07 billion, their sum exceeds Integer.MAX_VALUE (2,147,483,647). The overflow wraps to a negative number, dividing by 2 yields a negative index. For example, left=1,500,000,000 and right=1,600,000,000 gives sum=3,100,000,000, which overflows to -1,194,967,296, and dividing by 2 gives -597,483,648. This bug lived in Java's Arrays.binarySearch() implementation for nearly a decade — Joshua Bloch reported it in 2006, and it affected every Java program using the standard library's binary search on very large arrays.
Fix
Replace (left + right) / 2 with left + (right - left) / 2. This never overflows because right - left is always non-negative and always less than Integer.MAX_VALUE when both are valid array indices. Alternatively use (left + right) >>> 1 (unsigned right shift), which treats the bit pattern as unsigned and correctly halves even overflowed sums. Java's standard library was patched to use the unsigned shift approach.
Key lesson
  • Never assume addition of two positive integers stays positive in languages with fixed-width integers (Java, C, C++, C#).
  • Use the safe midpoint formula as a reflex — it's mathematically identical and overflow-proof.
  • Test binary search on edge cases: largest possible array sizes, first/last elements, single-element arrays, and missing values.
  • Python developers are not immune — while Python handles arbitrary-precision integers natively, the same bug exists in C extensions and in any language with fixed-width integers.
  • Code review should flag (left + right) / 2 as a known anti-pattern in any binary search implementation.
Production debug guideCommon failure modes and how to diagnose them fast5 entries
Symptom · 01
Search returns -1 for a value you're certain exists in the array
Fix
Check if the array is sorted. Run Arrays.sort(array) before searching. Verify with a linear scan on a small subset. Also check whether the sort order matches what the binary search expects — ascending vs descending sorts require different comparison logic.
Symptom · 02
ArrayIndexOutOfBoundsException: Index -597483648 out of bounds
Fix
Inspect the midpoint calculation. If using (left + right) / 2, replace with left + (right - left) / 2 immediately. This is integer overflow. Print left, right, and their sum to confirm the overflow is occurring.
Symptom · 03
Program hangs indefinitely (infinite loop)
Fix
Check boundary updates: ensure left = mid + 1 and right = mid - 1 (not left = mid or right = mid). With a 2-element window, mid equals left and if you set left = mid, the window never shrinks. Add a debug print of left, right, and mid each iteration to observe stagnation.
Symptom · 04
Search returns wrong index (finds a match but not the one you wanted)
Fix
Duplicates in the array? Standard binary search returns any occurrence, not necessarily the first or last. If you need the leftmost occurrence, modify the algorithm to continue searching left after a match (right = mid - 1 and record result). For rightmost, continue right (left = mid + 1 and record result).
Symptom · 05
Binary search works in testing but fails in production on the same data
Fix
Check if the data is mutated between sorting and searching. Concurrent modifications, live inserts, or background sorts that run asynchronously can break the sorted invariant. Add an assertion or checksum to verify sort order immediately before the search call.
★ Binary Search Quick Debug Cheat SheetThree failure modes and the exact commands to diagnose them
Wrong answer — target exists but returns -1
Immediate action
Print left, right, mid, and array[mid] each iteration to trace the logic
Commands
System.out.println("left=" + left + " right=" + right + " mid=" + mid + " val=" + arr[mid]);
for (int i = 1; i < arr.length; i++) { if (arr[i-1] > arr[i]) { System.out.println("UNSORTED at index " + i); break; } }
Fix now
Arrays.sort(arr); then retry. If the array was sorted descending, reverse the comparison logic in the binary search.
ArrayIndexOutOfBoundsException with large arrays+
Immediate action
Check whether the midpoint formula uses (left + right) / 2
Commands
System.out.println("left=" + left + " right=" + right + " sum=" + ((long)left + right) + " overflows_int=" + ((long)left + right > Integer.MAX_VALUE));
System.out.println("Max safe array size with buggy formula: " + (Integer.MAX_VALUE / 2));
Fix now
Replace (left + right) / 2 with left + (right - left) / 2. Alternatively use (left + right) >>> 1 for unsigned shift.
Program hangs — infinite loop+
Immediate action
Add a debug print at the end of the loop body to observe whether boundaries change each iteration
Commands
System.out.println("End of iteration: left=" + left + " right=" + right + " mid=" + mid);
// Check: when left=2, right=3, mid=2, does left advance? If left = mid (not mid+1), it stays at 2 forever.
Fix now
Change left = mid to left = mid + 1. Change right = mid to right = mid - 1. Both boundaries must strictly advance past mid.
Binary Search vs Alternatives
AlgorithmTime Complexity (Best/Avg/Worst)SpaceRequirementBest Use Case
Binary SearchO(1) / O(log n) / O(log n)O(1) iterative, O(log n) recursiveSorted, random access (array/ArrayList)Large static sorted arrays, repeated searches, database indexing, optimisation problems
Linear SearchO(1) / O(n/2) / O(n)O(1)None — works on any data structure, sorted or unsortedSmall arrays (<64 items), unsorted data, single search, linked lists, fuzzy matching
Hash Table (HashMap)O(1) / O(1) / O(n) collision worst caseO(n)Good hash function, extra memory for tableFast exact-match lookups, dynamic data with frequent inserts/deletes, no ordering needed
Balanced BST (TreeSet/TreeMap)O(log n) / O(log n) / O(log n)O(n)Comparable elements, dynamic insertionsOrdered data with frequent insertions and deletions, range queries, floor/ceiling lookups
Interpolation SearchO(1) / O(log log n) / O(n)O(1)Sorted, uniformly distributed, random accessLarge uniformly distributed datasets (e.g., evenly spaced numeric IDs)

Key takeaways

1
Binary search runs in O(log n) time but requires the array to be sorted first
unsorted input silently returns wrong answers with no error or exception.
2
Always use the safe midpoint formula left + (right - left) / 2 to prevent integer overflow. The bug from (left + right) / 2 hid in Java's standard library for nearly a decade.
3
The loop condition must be left <= right with boundary updates left = mid + 1 and right = mid - 1; using left < right misses single-element windows, and using left = mid causes infinite loops.
4
For duplicates, use modified binary search to find first or last occurrence
standard binary search returns an arbitrary matching index.
5
Binary search isn't just for arrays
use it on any monotonic search space (optimisation problems, feasibility checks, root finding). The 'binary search on the answer' pattern transforms optimisation into repeated feasibility checking.
6
For tiny arrays (<64 items), linear search often wins due to CPU cache locality. For dynamic data, use a self-balancing tree (TreeMap). Match the tool to the access pattern.
7
In production, prefer language built-ins (Arrays.binarySearch, bisect, std::lower_bound) and hand-roll only for custom variants (first/last occurrence, rotated arrays, search on answer).

Common mistakes to avoid

5 patterns
×

Applying binary search on an unsorted array

Symptom
The search returns -1 even though you're certain the target exists. No exception, no warning — just a wrong answer. Tests on small sorted datasets pass; the bug only surfaces in production when unsorted data flows in.
Fix
Always call Arrays.sort(array) before searching, or assert that the array is sorted in debug mode: for (int i=1; i<arr.length; i++) assert arr[i-1] <= arr[i];. If data mutates between sort and search, re-sort or use a self-sorting data structure (TreeSet, TreeMap).
×

Integer overflow in midpoint calculation using (left + right) / 2

Symptom
ArrayIndexOutOfBoundsException with a negative index like -597483648 when the array size exceeds ~1 billion elements. Works perfectly on smaller arrays, making it nearly impossible to catch in typical unit tests.
Fix
Replace (left + right) / 2 with left + (right - left) / 2 or use unsigned shift (left + right) >>> 1 in Java. Both produce the same result but never overflow. This applies to any language with fixed-width integer types (Java, C, C++, C#, Rust).
×

Wrong boundary update causing infinite loop (left = mid instead of left = mid + 1)

Symptom
The program hangs indefinitely, CPU spikes to 100%, and the loop never terminates. Occurs when the search window narrows to 2 elements: left and left+1. Since mid = left (integer division floors), setting left = mid doesn't advance the window.
Fix
Always update left = mid + 1 when target > arr[mid], and right = mid - 1 when target < arr[mid]. Both boundaries must strictly exclude mid to guarantee the window shrinks every iteration.
×

Using `left < right` instead of `left <= right` in the loop condition

Symptom
The algorithm misses the target when the search window narrows to exactly one element (left == right). If the target happens to be that single remaining element, the loop exits without checking it, returning -1 incorrectly. This is an intermittent false negative that depends on where the target sits in the array.
Fix
Use while (left <= right) to include the single-element window check. The loop correctly terminates when left > right, meaning the entire search space has been exhausted.
×

Not handling duplicates — expecting first or last occurrence from standard binary search

Symptom
Standard binary search returns an arbitrary index among duplicate values, not the first or last. This causes bugs when the caller assumes the returned index is the first occurrence (e.g., for range queries, count calculations, or time-series earliest-event lookups).
Fix
Use modified binary search: for first occurrence, when arr[mid] == target, record mid and set right = mid - 1 to continue searching left. For last occurrence, set left = mid + 1 to continue searching right. See the First and Last Occurrence section.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Implement binary search iteratively and recursively. Explain the tradeof...
Q02JUNIOR
What is the integer overflow bug in binary search and how do you fix it?
Q03SENIOR
Search for a target in a rotated sorted array. What if there are duplica...
Q04SENIOR
Given a sorted array, find the starting and ending position of a given t...
Q05SENIOR
Find the integer square root of a number using binary search.
Q06SENIOR
Explain 'binary search on the answer' with an example. When does this pa...
Q01 of 06JUNIOR

Implement binary search iteratively and recursively. Explain the tradeoffs.

ANSWER
Iterative version uses a while loop with O(1) space. Recursive version calls itself with updated boundaries, using O(log n) stack space. Both run in O(log n) time. Iterative is preferred in production for predictable memory usage and easier debugging. Recursive is more readable and mirrors the algorithm's English description. ``java // Iterative — O(log n) time, O(1) space public int binarySearch(int[] arr, int target) { int left = 0, right = arr.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) return mid; else if (arr[mid] < target) left = mid + 1; else right = mid - 1; } return -1; } // Recursive — O(log n) time, O(log n) stack space public 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; else if (arr[mid] < target) return binarySearchRecursive(arr, target, mid + 1, right); else return binarySearchRecursive(arr, target, left, mid - 1); } ` Key details to mention: safe midpoint formula to avoid overflow, left <= right loop condition, and mid ± 1` boundary updates to prevent infinite loops.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Why does binary search require sorted data?
02
What's the difference between Arrays.binarySearch and Collections.binarySearch?
03
Can binary search be used on a linked list?
04
What's the safe midpoint formula and why does it work?
05
When should I use linear search instead of binary search?
06
How does binary search relate to database indexing?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Everything here is grounded in real deployments.

Follow
Verified
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
🔥

That's Searching. Mark it forged?

19 min read · try the examples if you haven't

Previous
Sorting Algorithm Comparison
1 / 8 · Searching
Next
Binary Search on Answer