intermediate9 min read·Updated October 6, 2026
Binary Search First Occurrence Explained: Finding 3 in [1,3,3,3,5]
Master the binary search lower bound pattern by tracing the first occurrence of 3 in [1,3,3,3,5] and the insert position for 2 in [1,3,5].
By Learnisim AI·Published October 6, 2026
LEARNING ARCSetup → Model in the example → Full trace → Twist → Edge cases → Apply
PREREQUISITES
- Basic binary search on unique sorted arrays
- Understanding of pointers or index bounds (low and high)
Why
Why Standard Binary Search Fails on Duplicates
Phase 1: The Duplicate Trap
Imagine you are handed a sorted list of numbers containing duplicates:
[1, 3, 3, 3, 5], and your mission is to find the first occurrence of the number 3. If you reach for a standard binary search, you calculate a midpoint, check if it equals 3, and happily return its index. But standard binary search has no loyalty to boundaries; it might land on index 2 or index 3 and stop immediately, completely oblivious to whether an earlier 3 is hiding to the left.Now consider a second scenario: what if the target is
2, which does not even exist in our sorted list [1, 3, 5]? A standard binary search will eventually report failure (-1 or not found). Yet, in many practical systems—like database indexing or language runtimes—we do not just want to know if 2 is present. We need its exact insert position, the index where 2 belongs to keep the sequence sorted. Without a specialized approach, you are forced to scan the array linearly, throwing away the speed advantage you bought by keeping the data sorted in the first place.Model
Building the Lower Bound Search Space and Invariant
Phase 2: The Core Model
Standard binary search stops as soon as it hits any matching value. When our sorted nums is and target is 3, a naive search might land on index 2 and declare success. But our goal for a lower bound is much stricter: we want the first occurrence at index 1, or for the missing target 2 in , the exact insertion index 1 where 2 belongs without breaking order.
To achieve this, we redefine our search space boundaries. Instead of setting
hi = n - 1, we maintain our high pointer at hi = n. This allows our pointer to validly point to the index after the last element, which is essential if our target is larger than all elements in the array. Our search window is always a half-open range .When we compute , we compare against our target. If , we know and everything to its left is strictly too small. We safely advance . However, if , itself could be our answer or our first occurrence might still lie further to the left. Therefore, we do not step past ; instead, we pull our upper bound down by setting .
Try this: Given nums = [1, 3, 5] and target = 2, what are the initial values of lo, hi, and mid on the very first iteration using this lower bound model?
Worked example
Tracing Lower Bound Execution on Duplicates and Missing Elements
Phase 3:
To see how the lower bound invariant operates in practice, let us trace our locked example step by step. We have a sorted array
nums = [1, 3, 3, 3, 5] and a target of 3. We want to locate its first occurrence. We initialize our search pointers across the full valid index range: lo = 0, hi = 5 (where ).Given:
Steps:
- Iteration 1:
- Iteration 2:
- Iteration 3:
- Termination: Now
nums = [1, 3, 3, 3, 5], target = 3 Steps:
- Iteration 1:
lo = 0, hi = 5. Compute mid = (0 + 5) // 2 = 2. Inspect nums[2], which is 3. Since nums[2] >= target (), we must look to the left to see if an earlier 3 exists. We set hi = mid (). - Iteration 2:
lo = 0, hi = 2. Compute mid = (0 + 2) // 2 = 1. Inspect nums[1], which is 3. Again, nums[1] >= target, so we set hi = mid (). - Iteration 3:
lo = 0, hi = 1. Compute mid = (0 + 1) // 2 = 0. Inspect nums[0], which is 1. Since 1 < 3, nums[0] is too small. We advance the lower bound: lo = mid + 1 (). - Termination: Now
lo = 1 and hi = 1. The loop condition lo < hi fails ( is false). We terminate and return lo.Result: Index
1, which is the exact first position of 3.Now let us trace the second part of our working example: finding the insert position of
Given:
Steps:
- Iteration 1:
- Iteration 2:
- Termination:
target = 2 in nums = [1, 3, 5]. Given:
nums = [1, 3, 5], target = 2, lo = 0, hi = 3 () Steps:
- Iteration 1:
lo = 0, hi = 3. Compute mid = (0 + 3) // 2 = 1. Inspect nums[1], which is 3. Since 3 >= 2, we set hi = 1. - Iteration 2:
lo = 0, hi = 1. Compute mid = (0 + 1) // 2 = 0. Inspect nums[0], which is 1. Since 1 < 2, we set lo = mid + 1 (). - Termination:
lo = 1 and hi = 1. Loop terminates.Result: Index
1, which correctly represents the insertion index where 2 belongs between 1 and 3 to keep the array sorted.Practice
Predicting Lower Bound Behavior on Edge Inputs
Phase 4: Practice
Now that you have traced how the invariant shrinks the window for both the first occurrence of 3 in
[1, 3, 3, 3, 5] and the missing insertion point of 2 in [1, 3, 5], it is time to test your mental model on a boundary modification of the same data. Recall that the lower bound routine returns the first index where , or if no such element exists.Consider the original array
[1, 3, 3, 3, 5] from our locked example, but change the target value to 0. Your task is to trace or mentally execute the lo and hi pointer updates to determine what index the algorithm will return and why the invariant prevents the pointers from breaking.Apply
Transferring Lower Bound Logic to Range Queries and Beyond
Phase 5: Applying the Lower Bound Pattern
Now that you have traced the lower bound logic for
[1, 3, 3, 3, 5] and handled the missing element 2 in [1, 3, 5], it is time to deploy this exact structural invariant to a new situation. Many problems that appear distinct—such as finding the frequency of a repeated element or locating the first bad version in a sequence of software builds—are actually direct disguises of the lower bound pattern.For instance, suppose you need to count how many times the value 3 appears in the locked array
[1, 3, 3, 3, 5]. Instead of scanning linearly, you can combine two binary searches: find the lower bound of 3 to lock down the starting index, and find the lower bound of (or an upper bound) to find the cutoff index. The difference between these two pointers yields the exact count in time.The core transfer skill is recognizing when a problem asks for a boundary transition from false to true (or less-than to greater-than-or-equal). Whenever you spot a sorted domain where you need the first transition point rather than any arbitrary match, throw away standard equality checking and instantiate the range with
hi = n and else hi = mid.The Final Challenge
Consider a sorted array of timestamps representing successful and failed system checks, where
[0, 0, 0, 1, 1] denotes 0 for pass and 1 for fail. You need to find the exact index of the first system failure (1). State how you would adapt the lower bound template variables (target = 1, comparison operator, and pointer updates) to solve this without modifying the core invariant.FAQ
What is the lower bound of 3 in [1, 3, 3, 3, 5]?
The lower bound is index 1, which points to the very first occurrence of the number 3 in the array.
How does lower bound handle a missing target like 2 in [1, 3, 5]?
When the target is missing, the lower bound returns the index where the target would be inserted to maintain sorted order, which is index 1 (between 1 and 3).
Why does standard binary search fail when duplicates are present?
Standard binary search stops immediately upon finding any matching element, which could be any middle duplicate rather than the earliest one.
What is the time complexity of a lower bound binary search?
It operates in O(log n) time complexity because the search space is halved at each step, just like standard binary search.