Lower bound and upper bound look almost identical, but one character difference (< vs <=) changes everything. Confusing them leads to wrong answers on "find first/last occurrence" problems.
This guide shows the exact difference, common mistakes, and a simple way to remember which is which.
TL;DR
Lower bound: First element >= target
if arr[mid] < target: # <
left = mid + 1
else:
right = midUpper bound: First element > target
if arr[mid] <= target: # <=
left = mid + 1
else:
right = midThe only difference: < vs <=
Definitions
Lower Bound
First position where element >= target
Array: [1, 2, 2, 2, 3, 4]
0 1 2 3 4 5
lower_bound(2) = 1 (first 2)
lower_bound(2.5) = 4 (first element >= 2.5 is 3)Upper Bound
First position where element > target
Array: [1, 2, 2, 2, 3, 4]
0 1 2 3 4 5
upper_bound(2) = 4 (first element > 2 is 3)
upper_bound(2.5) = 4 (first element > 2.5 is 3)Visual Comparison
Array: [1, 2, 2, 2, 3, 4]
↑ ↑
lower(2) upper(2)
lower_bound(2) = 1 (first >= 2)
upper_bound(2) = 4 (first > 2)
Last occurrence of 2 = upper_bound(2) - 1 = 3Common Mistake 1: Returning Wrong Pointer
The Problem
Returning mid instead of left after the loop.
❌ Wrong:
def lower_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid
return mid # WRONG: mid is not defined after loop✅ Correct:
def lower_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid
return left # Correct: left == right when loop endsCommon Mistake 2: Wrong Pointer Update
The Problem
Using right = mid - 1 instead of right = mid.
❌ Wrong:
def lower_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid - 1 # WRONG: excludes potential answer
return leftWhy it's wrong: arr[mid] might be the first occurrence. We can't exclude it.
✅ Correct:
right = mid # Keep mid as potential answerCommon Mistake 3: Confusing the Two
The Problem
Using lower bound template for upper bound (or vice versa).
❌ Wrong (using lower bound for upper bound):
def upper_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] < target: # WRONG: should be <=
left = mid + 1
else:
right = mid
return left✅ Correct:
if arr[mid] <= target: # Correct: exclude elements <= target
left = mid + 1Side-by-Side Template Comparison
Lower Bound
def lower_bound(arr, target):
"""First element >= target"""
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] < target: # Exclude elements < target
left = mid + 1
else: # arr[mid] >= target
right = mid # Keep as potential answer
return leftUpper Bound
def upper_bound(arr, target):
"""First element > target"""
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] <= target: # Exclude elements <= target
left = mid + 1
else: # arr[mid] > target
right = mid # Keep as potential answer
return leftThe only difference: Line 7: < vs <=
How to Remember
Mnemonic 1: "Equal Sign"
- Lower bound:
<(no equal) → keeps elements = target - Upper bound:
<=(has equal) → excludes elements = target
Mnemonic 2: "What We're Finding"
- Lower bound: First >= target → exclude < target
- Upper bound: First > target → exclude <= target
Mnemonic 3: "Condition Matches Goal"
- Lower bound: Find >= → exclude <
- Upper bound: Find > → exclude <=
Finding First and Last Occurrence
Using Both Bounds
def find_first_last(arr, target):
# First occurrence = lower_bound
first = lower_bound(arr, target)
# Check if target exists
if first == len(arr) or arr[first] != target:
return [-1, -1]
# Last occurrence = upper_bound - 1
last = upper_bound(arr, target) - 1
return [first, last]
# Example
arr = [5, 7, 7, 8, 8, 10]
print(find_first_last(arr, 8)) # [3, 4]Why This Works
Array: [5, 7, 7, 8, 8, 10]
0 1 2 3 4 5
Target = 8:
lower_bound(8) = 3 (first >= 8)
upper_bound(8) = 5 (first > 8)
last occurrence = 5 - 1 = 4 ✓Practice Examples
Example 1: Find First Occurrence
arr = [1, 2, 2, 2, 3, 4]
target = 2
lower_bound(arr, 2) = 1 # First 2 at index 1 ✓Example 2: Find Last Occurrence
arr = [1, 2, 2, 2, 3, 4]
target = 2
upper_bound(arr, 2) = 4 # First > 2 at index 4
last = 4 - 1 = 3 # Last 2 at index 3 ✓Example 3: Target Doesn't Exist
arr = [1, 2, 4, 5]
target = 3
lower_bound(arr, 3) = 2 # Would insert at index 2
upper_bound(arr, 3) = 2 # SameDebugging Checklist
When your bounds are wrong:
-
Using
<for lower bound,<=for upper bound? -
Returning
left(notmid)? -
Using
right = mid(notmid - 1)? -
Initializing
right = len(arr)(notlen(arr) - 1)? -
Using
left < right(notleft <= right)?
FAQ
Q: Why is it called "lower bound" and "upper bound"?
A: Lower bound is the lower boundary of where target could be (first >=). Upper bound is the upper boundary (first >).
Q: Can I use these on unsorted arrays?
A: No. Like all binary search, they require sorted data.
Q: What if target is larger than all elements?
A: Both return len(arr) (insertion position at end).
Q: What if target is smaller than all elements?
A: Both return 0 (insertion position at start).
Q: How do I find the count of target?
A: count = upper_bound(target) - lower_bound(target)
Conclusion
Lower bound and upper bound differ by one character, but that character matters.
Key differences:
- Lower bound: First >= target, exclude < target
- Upper bound: First > target, exclude <= target
- Code difference:
<vs<=
Common mistakes:
- Returning
midinstead ofleft - Using
right = mid - 1instead ofright = mid - Confusing which condition to use
How to remember:
- Lower bound:
<(no equal) - Upper bound:
<=(has equal)
Finding occurrences:
- First = lower_bound
- Last = upper_bound - 1
- Count = upper_bound - lower_bound
Master these two templates and you'll handle duplicates with confidence. For more details, see Find First and Last Position Template and the Complete Binary Search Guide.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
