Hello! Welcome to your fourth lesson.
In our last session, we built a robust, bug-free implementation of binary search for finding an exact target. You learned why details like loop conditions (left <= right) and pointer updates (mid + 1 / mid - 1) are critical for correctness.
Today, we'll adapt that core algorithm to solve more nuanced problems. Instead of just asking "Is the target here?", we will ask "Where should the target be?" and "Where does the range of this target begin and end?". This lesson directly addresses the learning outcome: Apply binary search to find insertion positions and search ranges in sorted arrays. Mastering these variations is a key step in leveling up your problem-solving skills for coding interviews.
1. The Lower Bound: Finding the Insertion Point
Our standard binary search stops as soon as it finds a match. But what if we need to find the first possible position for a target? This is useful for two scenarios:
- Finding the index to insert a new element while maintaining sorted order.
- Finding the starting index of a target that might have duplicates.
This position is formally known as the lower bound.
Lower Bound: The smallest index i in a sorted array such that nums[i] >= target.

To find the lower bound, we modify our binary search logic: even when we find an element nums[mid] that is >= target, we don't stop. We record this mid as a potential answer and then try to find an even smaller index to its left.
Let's watch a video that explains this concept and walks through the implementation logic.
BS-2. Implement Lower Bound and Upper Bound | Search Insert Position | Floor and Ceil
This video from 'take U forward' provides an excellent, detailed explanation of the lower bound concept and how to implement it using a modified binary search.
Please watch from 00:00 to 02:40 to understand the definition, and then from 03:35 to 13:41 for a detailed dry-run and explanation of the code template. Focus on how the ans variable is updated and why the search continues to the left even after a potential answer is found.
Here is a Python implementation of the lower_bound function based on the logic from the video:
def lower_bound(nums, target):
"""
Finds the first index i such that nums[i] >= target.
Returns len(nums) if all elements are smaller than target.
"""
left, right = 0, len(nums) - 1
ans = len(nums) # Default answer if no element is >= target
while left <= right:
mid = left + (right - left) // 2
if nums[mid] >= target:
# This is a potential answer. Store it and look for a better one (smaller index) to the left.
ans = mid
right = mid - 1
else: # nums[mid] < target
# The answer must be in the right half.
left = mid + 1
return ans
This exact logic solves the LeetCode problem "Search Insert Position" (LC 35). If the target is in the array, lower_bound will find its first occurrence. If it's not, it will find the index where it should be inserted. The video confirms this connection in the segment starting at 19:36.
2. Finding a Search Range: First and Last Position
A very common interview problem (LeetCode 34: Find First and Last Position of Element in Sorted Array) asks you to find the starting and ending index of a target value that may appear multiple times.
For example, in [5, 7, 7, 8, 8, 10], for target = 8, the answer is [3, 4].
We can solve this by performing two modified binary searches:
- Find the first occurrence: This is simply the
lower_boundof the target. - Find the last occurrence: We can do this by finding the
lower_boundoftarget + 1and subtracting 1 from the result. This clever trick finds the first element greater than our target, and the element right before it must be the last occurrence of our target.
This "upper bound" concept is the natural counterpart to the lower bound.
Upper Bound: The smallest index i in a sorted array such that nums[i] > target.
The implementation is nearly identical to lower_bound, with only a change from >= to >. The video you watched earlier also covers this.
BS-2. Implement Lower Bound and Upper Bound | Search Insert Position | Floor and Ceil
Let's revisit the 'take U forward' video to see how the upper bound is defined and implemented. This is the second piece of the puzzle for finding a search range.
Watch from 15:34 to 19:14. Notice the single-character change in the condition (> instead of >=) to implement the upper bound search.
Now we can combine these two functions to solve the search range problem.
def search_range(nums, target):
# Find the first occurrence of the target
start = lower_bound(nums, target)
# If the start index is out of bounds or the element at start is not the target,
# then the target is not in the array.
if start == len(nums) or nums[start] != target:
return [-1, -1]
# Find the first occurrence of an element > target
# This is the upper_bound of the target.
# The last occurrence of the target will be the index right before it.
end_index_exclusive = lower_bound(nums, target + 1)
end = end_index_exclusive - 1
return [start, end]
# lower_bound function as defined before
def lower_bound(nums, target):
left, right = 0, len(nums) - 1
ans = len(nums)
while left <= right:
mid = left + (right - left) // 2
if nums[mid] >= target:
ans = mid
right = mid - 1
else:
left = mid + 1
return ans
# Example:
nums = [5, 7, 7, 8, 8, 10]
target = 8
print(search_range(nums, target)) # Output: [3, 4]
target = 6
print(search_range(nums, target)) # Output: [-1, -1]
For a comprehensive text-based walkthrough of this problem, including the intuition, pitfalls, and code in multiple languages (Python, Java, C++), the following resource is excellent.
34. Find First and Last Position of Element in Sorted Array
This AlgoMonster article provides a deep dive into LeetCode 34. It's a great supplement to the video explanations.
Read the sections 'Intuition', 'Solution Approach', 'Example Walkthrough', and 'Common Pitfalls'. This will solidify your understanding of the two-search strategy and help you avoid common mistakes.
Test your understanding!
Given the array nums = [1, 2, 4, 4, 4, 4, 9, 11] and target = 4, trace the search_range function.
- What will
lower_bound(nums, 4)return? - What will
lower_bound(nums, 5)return? - What is the final result?
Show answer
lower_bound(nums, 4)will search for the first indexiwherenums[i] >= 4. It will find index2. So,start = 2.lower_bound(nums, 5)will search for the first indexiwherenums[i] >= 5. It will find index6(where the value is 9). So,end_index_exclusive = 6.- The
endindex isend_index_exclusive - 1, which is6 - 1 = 5. The final result is[start, end], which is[2, 5]. This correctly identifies the range of all 4s.
Conclusion
You have now extended your binary search skills beyond simple lookups. By making small but crucial modifications to the comparison logic, you can find boundaries and ranges efficiently.
Key Takeaways:
- Lower Bound:
lower_bound(target)finds the first indexiwherenums[i] >= target. This is the key to solving "Search Insert Position". - Upper Bound:
upper_bound(target)finds the first indexiwherenums[i] > target. - Search Range Strategy: The range of a target can be found by
[lower_bound(target), upper_bound(target) - 1], after checking that the target exists. - The Pattern: The core pattern is to not stop on a match, but to record it as a potential answer and continue shrinking the search space to find an even better (leftmost or rightmost) solution.
In our next lesson, we will push this pattern further by applying binary search to a more complex data structure: a rotated sorted array. This will challenge you to adapt the boundary conditions in a new way.