Binary Search
Lower & Upper Bound (Variations)
Master the binary search boundary templates to find first/last occurrences, insertion points, and element ranges.
Last Updated: August 2, 2026
•
20 min read
1. Introduction
What are Boundary Binary Searches?
In arrays containing duplicate elements, a standard exact-match search returns an arbitrary matching index. Lower Bound and Upper Bound are variations that locate the precise boundaries of duplicate ranges:arr[idx] >= target).arr[idx] > target).Why study them?
These variants define the insertion points to keep an array sorted. They also allow you to solve range query problems—such as "how many times does key X appear in the array?"—in logarithmic time.Where is it Used?
std::lower_bound and std::upper_bound algorithms.bisect.bisect_left and bisect.bisect_right modules.2. Mental Model: Finding Insertion Slots
Imagine you have a sorted bookshelf with books arranged by weight:
[ 2kg, 4kg, 4kg, 4kg, 6kg ]
You receive a new 4kg book and want to slide it into a slot so the shelf remains sorted:
4kg books (index 1).4kg books (index 4).3. Core Concepts & Implementations
Invariant & Boundary Updates
In boundary searches:hi = nums.length (not length - 1). If the target is larger than all elements, the correct insertion point is at the very end of the array.while (lo < hi). The search terminates when lo == hi.arr[mid] >= target, then mid is a candidate, but we want to look for a smaller index. We update hi = mid. Otherwise, we search the right half: lo = mid + 1.arr[mid] > target, mid is a candidate. We set hi = mid. Otherwise, lo = mid + 1.4. Visual Trace: Lower Bound vs Upper Bound
Let's trace both bounds for target = 4 on array [2, 4, 4, 4, 6]:
5. Real-World Applications
X in a sorted array, run both bounds:Count = upperBound(X) - lowerBound(X)
pre on an sorted array of strings is equivalent to finding the lower bound for string pre and upper bound for pre + '{'.6. Interview Perspective
How Interviewers Ask This Topic
Common Mistakes
Warning: 1. Off-by-one Termination: Using
while (lo <= hi) with hi = mid creates an infinite loop when lo == hi.> 2. Wrong Upper Bound Invariant: Writing>= targetinside an Upper Bound search. Upper bound must use> targetstrictly.
7. Summary
arr[i] >= target.arr[i] > target.hi must initialize to array.length to allow insertion at the end index.while (lo < hi) and terminates when lo == hi.8. Quiz
Question 1: What is the output of lowerBound and upperBound for target = 10 in array [2, 5, 8]?
Answer: Both return3. Since 10 is larger than all elements, the loop terminates at index 3 (the length of the array), suggesting that the element should be inserted at the end.
Question 2: How do you find the last occurrence of an element X in a sorted array using lowerBound?
Answer: You can find the first occurrence ofX + 1 by executing lowerBound(X + 1). The last occurrence of X is at lowerBound(X + 1) - 1 (assuming X exists in the array).
Question 3: If you use the standard template, what is the value of lo and hi when the loop terminates?
Answer:lo is equal to hi. They point to the exact same insertion index.
Question 4: True or False: upper_bound(target) - lower_bound(target) always returns the count of target in the array.
Answer: True. If the element doesn't exist, both bounds return the same insertion index, making the count0.
Question 5: Why is it crucial to set hi = nums.length instead of nums.length - 1?
Answer: Because if the target is larger than all elements, it must be inserted at indexnums.length. If hi is initialized to nums.length - 1, we can never reach the final index.