Find a boundary instead of an exact match
Binary search can locate the first position where a predicate changes from false to true. Searching for the first value at least a target handles missing values and duplicates without special cases. Use a half-open interval: the answer always lies between left and right, and right may be the length of the list.
from bisect import bisect_left
print(bisect_left([2, 4, 4, 9], 4))Think before coding
Every iteration keeps the first valid boundary inside the remaining interval. Moving right to middle preserves a possible answer, while moving left past middle removes only values known to be too small. Work through the example by hand before changing the starter. Decide what should happen for an empty input, a single value and a repeated value where those cases apply. The checks use several inputs so a hard-coded answer cannot stand in for the rule.
Your task
Implement lower_bound(items, target) without importing bisect. Return the first index with items[index] >= target in a sorted list, or len(items) if every value is smaller.
The explanation opens here once every check passes.