The bisect module finds positions in sorted lists using binary search. It works well for frequent lookups in modest sequences, but it does not make insertion into a Python list inexpensive.
Choose an insertion side
from bisect import bisect_left, bisect_right, insort
scores = [5, 7, 7, 9]
assert bisect_left(scores, 7) == 1
assert bisect_right(scores, 7) == 3
insort(scores, 8)
assert scores == [5, 7, 7, 8, 9]
bisect_left points before equal values and bisect_right after them. The choice matters for intervals, pagination, and tie-breaking. The sequence must stay sorted under the same rule.
Search objects by key
from bisect import bisect_left
products = [
{"name": "Basic", "price": 50},
{"name": "Pro", "price": 120},
]
position = bisect_left(products, 100, key=lambda item: item["price"])
assert position == 1
The searched value is the key, so it is 100, not a dictionary. If key calculation is expensive and lookup repeats, consider a parallel key list or a carefully bounded cache.
Turn an insertion point into a lookup
The bisect functions return an insertion point; they do not confirm that an item exists. To locate an occurrence, calculate the index and validate the value:
from bisect import bisect_left
def find(values: list[int], target: int) -> int:
index = bisect_left(values, target)
if index != len(values) and values[index] == target:
return index
raise ValueError(f"{target} not found")
The boundary check matters because a target larger than every item produces len(values). The same pattern implements membership, the first item greater than or equal to a target, and other boundary queries without hand-writing binary search.
bisect_left(values, x) divides the list into two regions: values before the index are less than x, while values from the index onward are greater than or equal to it. bisect_right() includes equal values in the left region. Thinking in these guarantees prevents interval mistakes.
Query ranges with two boundaries
Two insertion points delimit all values in a closed interval:
from bisect import bisect_left, bisect_right
def between(values: list[int], minimum: int, maximum: int) -> list[int]:
start = bisect_left(values, minimum)
end = bisect_right(values, maximum)
return values[start:end]
assert between([2, 4, 4, 7, 9, 12], 4, 9) == [4, 4, 7, 9]
Finding boundaries costs O(log n), but creating the slice takes time and memory proportional to the output. If only a count is needed, use end - start. To iterate without copying, use indexes or itertools.islice.
Open and closed boundaries follow naturally from the left and right variants. For [minimum, maximum), use bisect_left for both ends. For (minimum, maximum], use bisect_right for both.
Insert records with key
insort_left() and insort_right() also accept key. The key is applied to list items and to the item being inserted during search. Unlike a bisect_left() query, pass the complete record to insort:
from bisect import insort_right
events = [
{"time": 10, "name": "start"},
{"time": 20, "name": "finish"},
]
insort_right(
events,
{"time": 20, "name": "audit"},
key=lambda event: event["time"],
)
For equal keys, insort_right places the new record after existing ones. That alone is not a broad stability guarantee if other code reorders the collection. When tie-breaking matters, use a composite key such as (time, sequence) and manage the sequence explicitly.
The key parameter was added in Python 3.10. If a library supports an older version, keep parallel keys or choose another structure. Check the project's actual minimum version before publishing reusable code.
Understand the complete cost
Binary search examines about log2(n) positions. A Python list, however, is a contiguous array of references. Inserting in the middle shifts later elements, so insort is dominated by O(n). This is a good trade when reads greatly outnumber inserts, but can become a bottleneck with thousands of middle insertions.
Sorting once with sorted() is usually better when all data arrives as a batch. Inserting each of n incoming items with insort can approach quadratic work. Sorting the entire list after every item is wasteful too. Evaluate the actual pattern: batch or stream, read-to-write ratio, and maximum size.
If the requirement is repeatedly retrieving only the smallest or largest item, consider heapq. A heap does not keep every item sorted for range queries, but inserts and priority removals are O(log n). For concurrent access and persistence, a database index will often supply more appropriate guarantees.
Avoid recalculating expensive keys
During repeated searches, bisect may call key for the same list items and discard the results afterward. If the key performs parsing or normalization, keep a synchronized key list:
from bisect import bisect_left
names = ["Ana", "Erica", "Jo"]
keys = [name.casefold() for name in names]
new_name = "bruno"
index = bisect_left(keys, new_name.casefold())
keys.insert(index, new_name.casefold())
names.insert(index, new_name)
Two lists must be updated together from the application's perspective. Encapsulate them so callers cannot mutate only one. functools.cache can help when immutable objects repeat, but an unbounded cache has its own memory cost.
For human text, casefold() handles basic case differences, not every language's collation rules. When linguistic ordering is a requirement, use an appropriate collation tool and generate keys consistently.
Preserve invariants under mutation
Changing a field used for ordering after an object enters the list silently breaks the precondition. Remove and reinsert that record, or use immutable values. Do not mix types without a compatible total ordering.
The module's functions are not thread-safe when multiple threads operate on the same sequence. A concurrent mutation can produce undefined results and leave the list unsorted. Guard the sequence and compound operations with one lock, or assign modifications to one owner.
Even in one thread, “search then insert” is compound. Any mutation between those steps invalidates the index. insort performs both as one convenient call, but external synchronization remains necessary under concurrency.
Decide when bisect fits
Use bisect when a sequence is already sorted, comparisons share a consistent key, and lookups greatly outnumber inserts. It fits threshold tables, small time histories, range selection, percentile calculations over sorted data, and configuration lookup.
Test an empty list, targets before the first and after the last item, duplicates, and inclusive boundaries. Also test that the sequence remains sorted after every operation. Those cases document which side of equal values belongs to the contract.
A common application maps a continuous measurement to a category. Keep sorted upper thresholds, use bisect_right, and select a label with the returned index. Define behavior at an exact boundary and outside the range. This makes price bands, scoring tiers, and alert levels compact and auditable.
For immutable data queried by many requests, build and validate the sequence once during initialization. Do not call sorted() before every lookup, because that turns a logarithmic query into work dominated by O(n log n). If external input supplies thresholds, validate monotonic order and reject malformed configuration clearly.
Do not apply bisect directly to a generic iterator. Binary search requires indexed random access and a known length. Converting a large generator to a list may destroy the streaming memory advantage, so reconsider the algorithm for that case.
When benchmarking, include representative sizes and structure maintenance. Timing only bisect_left omits element shifts, key generation, locking, and slice copies, which are often the costs that dominate the real application.
Keep the comparison rule pure and deterministic. A key function that reads changing global state, current time, or external data can produce inconsistent decisions during one search. Precompute that state and pass stable records instead. Clear invariants matter more than saving a few lines: document how duplicates are placed, who may mutate the collection, and whether callers receive a copy or the original list.
Compare this with sorting Python lists. For frequent middle insertions, a database, heap, or specialized sorted structure may fit better.
The official bisect documentation, accessed July 28, 2026, covers preconditions, key, thread safety, and recipes. Measure the full operation: search is O(log n), while list insertion remains O(n).