skip to content

Why is `int mid = (low + high) / 2` a bug in binary search, and how do you fix it?

level: seniorimportance: should knowfreq 50%

answer

  1. low + high can exceed Integer.MAX_VALUE
  2. Overflow -> negative mid -> wrong/AIOOBE
  3. Fix: low + (high - low) / 2
  4. Alt: (low + high) >>> 1 (unsigned shift)
  5. Famous JDK Arrays.binarySearch bug

basics

~10 s

For large arrays, low + high can exceed Integer.MAX_VALUE and overflow to a negative number, so mid becomes wrong (often negative). Fix it with low + (high - low) / 2, which never overflows.

solid answer

~50 s

In binary search, `(low + high) / 2` first computes `low + high`. When both indices are large — possible for arrays bigger than about a billion elements, or whenever the sum exceeds Integer.MAX_VALUE — that addition silently overflows via two's-complement wraparound and becomes negative. Dividing a negative number by 2 yields a negative `mid`, so the array access throws ArrayIndexOutOfBoundsException or the search misbehaves. This is a real, famous defect that lived for years in the JDK's own Arrays.binarySearch. The standard fix is `int mid = low + (high - low) / 2;`: since `high >= low`, the difference is non-negative and at most `high`, so the intermediate never overflows, and adding back `low` stays within range. Alternatives include `(low + high) >>> 1` (unsigned right shift, which treats the wrapped bits correctly) or using `long`. It is a classic illustration that silent integer overflow causes correctness bugs, not just edge-case curiosities.

code

java · 8 lines
java
// BUG: overflows to a negative mid for large indices
int mid = (low + high) / 2;

// FIX 1: difference form
int safe = low + (high - low) / 2;

// FIX 2: unsigned right shift (JDK's choice)
int safeShift = (low + high) >>> 1;

go deeper

for a junior

Recognizes the fix low + (high - low) / 2 and that the original can break on big inputs.

for a middle

Explains that low + high overflows to a negative value causing a bad index, and why the difference form is safe.

for a senior

Knows the JDK history, enumerates fixes (difference form, >>> 1, long widening), and recognizes the pattern across sort/partition/pagination code.

for a principal

Treats it as evidence for a policy: review additive index/size math for overflow, add property/large-input tests, and standardize the safe idiom across the codebase.

## The setup: binary search and the midpoint Binary search repeatedly halves a sorted range `[low, high]` by examining the middle element. The natural way to write the midpoint is: ```java int mid = (low + high) / 2; ``` This is correct mathematically — but in fixed-width integer arithmetic it has a hidden overflow bug. ## Why it overflows `low` and `high` are array indices, so they are non-negative `int`s. But their **sum** can exceed `Integer.MAX_VALUE` (2,147,483,647). For example, if `low` and `high` are both around 1.5 billion (possible for very large arrays, or buffers, or any algorithm reusing this pattern on big numbers), `low + high` ≈ 3 billion, which overflows. Java integer overflow is **silent two's-complement wraparound**: the result becomes a **negative** number rather than throwing. Dividing that negative value by 2 yields a negative `mid`. Then `array[mid]` throws `ArrayIndexOutOfBoundsException`, or in index-arithmetic code the search simply produces wrong results. The insidious part: this code is correct for all small inputs and passes ordinary tests. It only fails at scale, so it can live in production for years — which is exactly what happened in the JDK. Josh Bloch documented that `Arrays.binarySearch` carried this bug for about nine years ("Nearly All Binary Searches and Mergesorts are Broken"). ## The fix ```java int mid = low + (high - low) / 2; ``` Why this is safe: because the loop invariant guarantees `high >= low`, the subexpression `high - low` is **non-negative** and is at most `high` (which is itself a valid index ≤ MAX_VALUE), so it cannot overflow. Halving it keeps it small, and adding `low` back lands between `low` and `high` — always in range. It computes the *same* midpoint without ever forming the dangerous large sum. ## Other valid fixes - **Unsigned shift:** `int mid = (low + high) >>> 1;`. The `>>>` operator shifts in a zero from the left and interprets all 32 bits as unsigned. Even when `low + high` overflows into the sign bit, the unsigned shift recovers the correct average for non-negative indices. This is what the JDK adopted. (Note: it relies on `low`, `high` being non-negative.) - **Wider type:** `int mid = (int)(((long) low + high) / 2);`. Promoting to `long` gives 64 bits of headroom so the sum can't overflow, then narrow back. - **Checked arithmetic:** `Math.addExact(low, high)` would *detect* the overflow (throw) rather than fix it — useful to surface the bug, not to compute the midpoint. ## The general lesson This is the canonical example that **silent integer overflow is a correctness bug, not an academic curiosity**. The same `(a + b) / 2` shape appears in merge sorts, range partitioning, hashing, and pagination math. Whenever you add two large same-signed integers, ask whether the sum can exceed the type's range, and prefer the difference form, a wider type, or unsigned shift. ## Key takeaway `(low + high) / 2` can overflow to a negative midpoint for large indices; use `low + (high - low) / 2` (or `(low + high) >>> 1`) to compute the average without overflowing.

  • Why is low + (high - low) / 2 guaranteed not to overflow?
    Since high >= low, (high - low) is non-negative and at most high (a valid index), so no intermediate exceeds Integer.MAX_VALUE; adding low back stays within [low, high].
  • How does (low + high) >>> 1 avoid the bug?
    The unsigned right shift treats the 32 bits as unsigned, so even when the sum overflows into the sign bit, dividing by two recovers the correct average for non-negative indices.

saying these in an interview costs you the question

  • Claiming it only matters for arrays > 2 billion elements (the sum, not the array size, overflows — well below that)
  • Saying it throws an overflow exception (it silently wraps to negative)
  • Offering (low + high) / 2 with long but forgetting to actually widen before the addition
  • Thinking >>> 1 works for arbitrary signed operands (it relies on non-negative indices)

context