Coding interview prep
May 15, 2026
Binary Search Patterns: The 3 Templates That Solve Every Variation
Binary search is one of the most elegant algorithms in computer science. It's also the one that produces more wrong implementations than almost any other. The reason is not the algorithm — it's that most engineers learn one template and then try to bend it to fit every variation until it breaks.
There are three distinct binary search templates, each built for a different kind of problem. Exact match, left boundary, right boundary. The algorithm inside each is virtually identical. What changes are two or three lines that encode what to do when the middle element is neither definitively correct nor definitively wrong. Those two lines are where off-by-one errors live.
This guide shows you the three templates side by side, explains the invariant behind each one, and gives you a decision framework for picking the right tool before you write a single line of code. By the end, binary search variations will stop feeling like a separate category and start feeling like the same idea expressed in three slightly different ways.
The mental model that changes everything
Most people think about binary search as "find the middle and check if it matches." That framing works for the simplest case but breaks down immediately when you need to find the first occurrence of a value, or the smallest index satisfying a condition, or the boundary between two regions.
The correct mental model is different: binary search is about eliminating half the search space at every step. You are not looking for a value — you are shrinking the set of positions where the answer could possibly exist. The loop terminates when that set contains exactly one candidate, or zero.
The core invariant
At every iteration, the answer — if it exists — lies within [left, right].
Every operation must preserve this invariant until left and right converge.
This is why `left = mid + 1` and `right = mid - 1` in the classic template. When you determine that `mid` is not the answer, you exclude it completely. But in boundary-finding templates, `mid` might still be a valid candidate — so you move `right = mid` instead of `mid - 1`, keeping `mid` inside the search space until it can be confirmed or eliminated by further narrowing.
That single distinction — whether to write `mid` or `mid ± 1` when updating a pointer — is responsible for the vast majority of binary search bugs in production code.
Watch the search space collapse — Template 1 (exact match) searching for 38:
Searching for 38 in a sorted array
Step 1/4: arr[4] = 16 < 38 → move left pointer right of mid
Each step eliminates one half of the remaining candidates. The teal cell is the current midpoint being evaluated. Once a half is greyed out, it can never contain the answer — the invariant guarantees this.
Template 1: Exact Match
Use this when the problem asks you to find a specific target value and return its index, or confirm whether it exists. The defining characteristic: if `arr[mid]` equals the target, you're done. There is no ambiguity about whether `mid` could be a better answer elsewhere.
Exact Match
Find a target value — terminate immediately on match
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1; // inclusive on both ends
while (left <= right) { // <= because a single element is valid
const mid = left + Math.floor((right - left) / 2); // avoids overflow
if (arr[mid] === target) return mid; // found — exit immediately
if (arr[mid] < target) left = mid + 1; // mid is too small, exclude it
else right = mid - 1; // mid is too large, exclude it
}
return -1; // target not present
}Key invariants
while (left <= right) — loop runs even when left === right (one candidate)
left = mid + 1 — mid is definitively wrong, exclude it
right = mid - 1 — mid is definitively wrong, exclude it
Use when
The problem asks: 'does target exist?', 'return the index of target', or any problem where finding an exact value terminates the search. The array must be sorted.
Classic problems
Why `left + (right - left) / 2` instead of `(left + right) / 2`
When `left` and `right` are both large integers, their sum can overflow a 32-bit integer. `left + (right - left) / 2` computes the same midpoint without overflow. In Python this doesn't matter — integers are arbitrary precision. In Java, C++, and Go it does. Writing it the safe way costs nothing and becomes a habit.
Template 2: Left Boundary
Use this when the problem asks for the first position satisfying a condition — the leftmost occurrence of a value in an array with duplicates, the smallest valid index, the first true in a predicate function. The key insight is that finding a valid candidate does not end the search, because there might be an equally valid candidate further to the left.
The template achieves this by setting `right = mid` instead of `right = mid - 1` when a candidate is found. This keeps `mid` inside the search space and pushes the right boundary toward it, shrinking the window while preserving the current best answer as a candidate.
Left Boundary
Find the first index where a condition becomes true
function leftBound(arr, target) {
let left = 0;
let right = arr.length; // exclusive upper bound — outside the array
while (left < right) { // < not <=, because right is out-of-bounds
const mid = left + Math.floor((right - left) / 2);
if (arr[mid] < target) left = mid + 1; // mid is too small, move past it
else right = mid; // mid is a candidate — keep it
}
// left === right — single candidate remains
return left < arr.length && arr[left] === target ? left : -1;
}Key invariants
right = arr.length — starts one past the end (no element at this index)
while (left < right) — loop ends when left === right (one candidate)
right = mid — mid is a valid candidate; keep it, shrink from the right
Use when
The problem asks: 'find the first occurrence', 'find the leftmost index', 'find the insertion position', 'given a monotonic predicate, find the first index where it's true'.
Classic problems
The "binary search on the answer" pattern
Template 2 is the foundation of a powerful interview technique: instead of binary searching on an array, you binary search on the answer space itself. "Koko Eating Bananas" and "Capacity to Ship Packages" don't have a sorted input array — they binary search on possible values (eating speed, ship capacity) until they find the minimum value that satisfies the constraint. The predicate is: does this value work? The array is the set of candidate answers. Recognizing this abstraction turns a category of "hard" problems into straightforward applications of Template 2.
Template 3: Right Boundary
Use this when the problem asks for the last position satisfying a condition — the rightmost occurrence, the largest valid index, the last true in a predicate. This template is the mirror of Template 2, with one asymmetry that most people get wrong: to avoid an infinite loop, the midpoint must round up instead of down when `left` and `right` are adjacent.
Right Boundary
Find the last index where a condition is true
function rightBound(arr, target) {
let left = 0;
let right = arr.length - 1; // inclusive upper bound
while (left < right) {
// Round UP to avoid infinite loop when right = left + 1
const mid = left + Math.ceil((right - left) / 2);
if (arr[mid] > target) right = mid - 1; // mid too large, exclude it
else left = mid; // mid is a candidate — keep it
}
return arr[left] === target ? left : -1;
}Key invariants
Math.ceil((right - left) / 2) — rounds up to prevent left = mid infinite loop
left = mid — mid is a valid candidate; keep it, shrink from the left
right = mid - 1 — mid is definitively wrong, exclude it
Use when
The problem asks: 'find the last occurrence', 'find the rightmost index', 'find the largest value satisfying a condition'. Also used as the second binary search in LC 34 (Find First and Last Position).
Classic problems
Why rounding up prevents the infinite loop
When `left = 4` and `right = 5`, floor division gives `mid = 4`. If `arr[4]` is a valid candidate, you set `left = mid = 4`. Nothing changed — the loop runs forever. Ceiling division gives `mid = 5`. Either `arr[5]` is valid (left moves to 5, loop ends next iteration) or it isn't (right moves to 4, loop ends). This is the single most common infinite loop bug in Template 3 implementations.
The decision framework
Before writing any code, two questions narrow the choice to one template with near-certainty. First: does the problem require finding an exact value, or a boundary position? Second: if it's a boundary, is it the leftmost or rightmost position that satisfies the condition?
Signal: "Find target value" / exact match required
while left <= right
Signal: "Find first position / index" / leftmost occurrence / first true
while left < right, shrink right
Signal: "Find last position" / rightmost occurrence / last true
while left < right, shrink left
Quick reference
while (left <= right) → exact match (left, right both inclusive)
while (left < right), right = mid → left boundary (right exclusive, round down)
while (left < right), left = mid → right boundary (right inclusive, round up)
The table above is the decision you should make before touching the keyboard. Spend thirty seconds on it. The template follows mechanically once the problem type is clear. Engineers who skip this step and "feel out" the template as they write are the ones who end up with subtle off-by-one bugs that pass the happy path and fail edge cases.
Off-by-one errors: where they actually come from
Off-by-one errors in binary search are not random. They come from exactly two sources: using the wrong template for the problem, and mismatching the loop condition with the pointer update.
Source 1: Template mismatch
Using Template 1 (while left <= right) for a boundary problem. When there are duplicates, the exact-match template may return any matching index, not the leftmost. The boundary templates exist precisely because the exact-match template cannot guarantee which occurrence it returns.
Source 2: Pointer/condition mismatch
Using `while (left < right)` with `right = mid - 1`. This will skip the candidate at `mid` when `left + 1 = right`, because `right` moves past `mid` before the loop can confirm it. Each template has one valid combination of loop condition and pointer updates — mixing them across templates produces bugs that are hard to trace.
The systematic fix is not to memorize which combinations are valid. It's to understand the invariant each template maintains and verify that every pointer update preserves it. For Template 1: after each step, the answer, if it exists, is strictly inside the new `[left, right]`. For Template 2: `right` is always a valid candidate or one position past the end. For Template 3: `left` is always a valid candidate or the leftmost position.
Testing with arrays of size 1 and 2 before submitting will catch the majority of edge case failures. A size-1 array tests whether your loop handles the base case without entering an infinite cycle. A size-2 array tests the adjacent-pointers case that trips up the rounding asymmetry in Template 3.
What interviewers actually look for
In a binary search interview, writing the algorithm correctly is expected. What separates strong candidates is the reasoning they articulate before and during the implementation.
Strong candidates state the invariant before writing the loop. Something like: "I'm using the left boundary template because I need the first index satisfying the condition, not just any index. My right pointer will always point to a valid candidate or one past the end." That sentence shows the interviewer you are reasoning about correctness, not pattern-matching syntax.
They also handle the edge cases explicitly: empty input, target smaller than all elements, target larger than all elements, all elements equal to target. These are one-liner checks once the main logic is correct — but candidates who don't understand the template deeply almost always forget them.
For "binary search on the answer" problems (Koko, Ship Packages, Minimum Days to Make Bouquets), the strongest signal you can give is stating the search space explicitly before binary searching it. "The answer must be between 1 and max(arr) — I'll binary search on that range and check whether each candidate satisfies the constraint." This reframe of the problem from "find a value in an array" to "find the minimum value satisfying a function" is exactly what the problem is testing.
Related patterns on Expora
Binary search is one of the core patterns that appear across the full set of coding interview problems. If you want the complete map, the 7 algorithm patterns that cover 80% of interview problems places binary search in context alongside sliding window, dynamic programming, and graph traversal.
For problems involving sorted subarrays and contiguous range optimization, sliding window and two pointers are the natural complement to binary search — both operate on sorted or ordered structures, and many problems require both patterns in combination.
Frequently asked questions
Expora
No credit card required · Early access