Python’s bisect module finds insertion points in sorted lists. It is useful when a program needs to search an ordered sequence or add new items while keeping that order intact.
The official Python documentation for bisect is the main reference for these functions. The Python Sorting HOWTO explains sort keys and ordering behavior, and the list documentation covers the list operations used below. Related PythonPool guides include NumPy searchsorted, sorting a list of lists, list length, and heapq priority queues.
Use bisect when the data is already sorted and the program needs positions. The module does not check that the list is sorted for you. If the input order is wrong, the returned position can also be wrong.
The two search functions are bisect_left() and bisect_right(). The left form returns the position before equal items. The right form returns the position after equal items. The shorter name bisect() is an alias for bisect_right().
The insert helpers are insort_left() and insort_right(). They first find the correct insertion point, then insert the item into the list. This keeps the list ordered, but insertion into the middle of a list still has to move later items.
That tradeoff matters. Searching with bisect is efficient, but inserting into a Python list can still cost time proportional to the list length. For a few updates or medium-sized ordered lists, the simple list approach is often excellent. For very heavy update workloads, a different data structure may fit better.
Think of bisect as a tool for ordered positions, not as a general search replacement. It shines when sorted order is part of the design: grade boundaries, time slots, thresholds, duplicate ranges, leaderboards, cut points, and ordered lookup tables.
Find Insertion Points
Use bisect_left() and bisect_right() to see where a target would fit in a sorted list.
import bisect
numbers = [10, 20, 20, 30, 40]
print(bisect.bisect_left(numbers, 20))
print(bisect.bisect_right(numbers, 20))
print(bisect.bisect(numbers, 20))
The left position points before the first matching 20. The right position points after the last matching 20.
This difference is important when duplicates are allowed. It lets you choose whether a new item should go before or after existing equal items.
If the target is not present, both functions return the same insertion point. That position can still be useful because it tells you where the target belongs.
Check Whether A Value Exists
bisect_left() can support an existence check by looking at the item at the returned position.
import bisect
def index_of(sorted_values, target):
position = bisect.bisect_left(sorted_values, target)
if position != len(sorted_values) and sorted_values[position] == target:
return position
return None
numbers = [4, 8, 15, 16, 23, 42]
print(index_of(numbers, 15))
print(index_of(numbers, 9))
The function returns an index only when the target is actually present. Otherwise it returns None.
This pattern is safer than assuming the insertion point is a match. The insertion point might be the place where the target would be inserted, not a position containing the target.
Use this style when you need a clear distinction between found and not found.
Keep A List Sorted With insort
Use insort() or insort_right() to insert after equal items. Use insort_left() to insert before equal items.
import bisect
scores = [70, 80, 90]
bisect.insort(scores, 85)
bisect.insort_left(scores, 70)
print(scores)
The list remains sorted after each insert.
This is convenient for ordered collections that receive occasional new items. It avoids calling sort() after every append.
Remember that the insertion step changes the list in place. If other code holds the same list, it will see the updated order too.
Count Values In A Range
Two bisect calls can count how many sorted values fall inside a range.
import bisect
def count_between(sorted_values, low, high):
left = bisect.bisect_left(sorted_values, low)
right = bisect.bisect_right(sorted_values, high)
return right - left
grades = [55, 70, 75, 80, 85, 90, 98]
print(count_between(grades, 70, 89))
The left boundary includes values equal to low. The right boundary includes values equal to high.
This technique is useful for buckets, score bands, timestamps, and threshold reports. Because it uses positions, it avoids scanning every item in the list.
Define the range rules carefully. If the upper boundary should be exclusive, use bisect_left() for the high side instead.
Insert Ordered Records With Tuples
Tuples are compared from left to right, so they work well when the first item is the ordering field.
import bisect
events = [
(10, "draft"),
(20, "review"),
(30, "ship"),
]
position = bisect.bisect_left(events, (25, "test"))
events.insert(position, (25, "test"))
print(events)
The new event is placed between the entries with order values 20 and 30.
This tuple approach keeps the example simple because the ordering value travels with the label. For larger records, keep the sort key consistent across every item.
If two records can share the same ordering value, decide whether new records should go to the left or right of matching records. That choice affects stable presentation.
Maintain A Running Sorted Sample
insort() can maintain a sorted list while items arrive one at a time.
from bisect import insort
def median(sorted_values):
middle = len(sorted_values) // 2
if len(sorted_values) % 2:
return sorted_values[middle]
return (sorted_values[middle - 1] + sorted_values[middle]) / 2
sorted_values = []
for item in [5, 1, 9, 3]:
insort(sorted_values, item)
print(sorted_values)
print(median(sorted_values))
The list is sorted after each arrival, so the median function can read from the middle positions directly.
This works well for small streams, examples, and cases where readable code matters more than specialized performance. For very large streams, measure the cost of middle insertions before choosing this approach.
In short, use bisect_left() when the left side of duplicates matters, bisect_right() when the right side matters, and insort() when a sorted list should stay ordered after insertion. Keep the input sorted, choose duplicate behavior deliberately, and remember that list insertion still moves items.