TL;DR
Lower-bound binary search (first index with nums[i] >= target) β O(log n) time, O(1) space.
Approach 1 β Brute force (linear scan)
Walk the array left to right and stop at the first element that is >= target; that index is the answer whether the target is present (equal) or absent (greater). If you never stop, the target belongs at the end.
class Solution:
def searchInsert(self, nums: list[int], target: int) -> int:
for i, x in enumerate(nums):
if x >= target:
return i
return len(nums)
Time O(n), space O(1). Passes at n <= 10^4, but the problem mandates O(log n) and the scan ignores the sortedness entirely.
Approach 2 β Lower-bound binary search
The insight: βindex of target, or where it would be insertedβ is one question in disguise: the first index whose element is >= target (length of the array if none). The predicate nums[i] >= target is False for a prefix and True for the rest β a monotone boundary β so binary search can find the first True. This boundary-finding variant is the classical lower bound binary search: it locates the leftmost position where a value could be inserted while keeping order.
class Solution:
def searchInsert(self, nums: list[int], target: int) -> int:
lo, hi = 0, len(nums) # hi is exclusive; answer may be len(nums)
while lo < hi:
mid = (lo + hi) // 2
if nums[mid] < target:
lo = mid + 1 # boundary is strictly right of mid
else:
hi = mid # mid could be the boundary
return lo
Walkthrough on nums = [1, 3, 5, 6], target = 2:
lo=0, hi=4 β mid=2, nums[2]=5 >= 2 β hi=2.
lo=0, hi=2 β mid=1, nums[1]=3 >= 2 β hi=1.
lo=0, hi=1 β mid=0, nums[0]=1 < 2 β lo=1.
lo == hi == 1 β return 1. β
Note there was no βfoundβ check anywhere β target 5 and target 2 flow through the identical code. Time O(log n), space O(1).
Approach 3 β The standard library (bisect_left)
The insight: lower bound is so fundamental that Python ships it: bisect.bisect_left(nums, target) returns exactly the leftmost insertion point that keeps nums sorted β the same value Approach 2 computes. bisect is the standard-library implementation of binary-search insertion points on sorted sequences.
import bisect
class Solution:
def searchInsert(self, nums: list[int], target: int) -> int:
return bisect.bisect_left(nums, target)
Walkthrough on nums = [1, 3, 5, 6], target = 7: every element is < 7, so the leftmost valid insertion point is 4, the array length. β
Time O(log n), space O(1). In an interview, write Approach 2 to show you can, then mention this one to show you know the ecosystem.
Common pitfalls
- Initializing
hi = len(nums) - 1 with the lo < hi convention β the βinsert at the very endβ answer (len(nums)) becomes unreachable, failing targets larger than every element.
- Handling βfoundβ and βnot foundβ as separate code paths. The lower-bound formulation makes them the same; special-casing invites off-by-ones.
- Using the
bisect_right boundary (first index with nums[i] > target) β with distinct values it differs from bisect_left exactly when the target is present, returning one past the match instead of the match.
- Mixing conventions:
hi = mid belongs with exclusive hi and while lo < hi; hi = mid - 1 belongs with inclusive hi and while lo <= hi. Blending them loops or skips.
Pattern takeaway
When a binary-search problem asks for a position rather than a match, reframe it as boundary-finding: define a monotone predicate (here nums at i >= target), then find the first index where it turns True. The half-open [lo, hi) lower-bound template handles found, not-found, and end-of-array cases with zero special-casing β itβs the single most reusable binary-search variant.