bisect: key= Only Transforms the List, Never the Search Value (tested)
bisect.bisect_left(a, x, key=f) (added in Python 3.10) looks like it would transform both sides of the comparison the same way sorted(data, key=f) or min(data, key=f) do. We measured exactly which side the key function actually runs on.
1. key() runs on every element of the list -- but never on x itself
import bisect
data = sorted(["fig", "kiwi", "apple", "mango"], key=len)
print(data, [len(s) for s in data])
# x=4 is already "in key-space" -- it's a length, not a string
idx = bisect.bisect_left(data, 4, key=len)
print(idx)
['fig', 'kiwi', 'apple', 'mango'] [3, 4, 5, 5]
1
Measured: this only works because 4 is passed in already transformed -- it's compared directly against each element's len(), not run through len() itself. Nothing about the call makes that obvious; key=len reads as "use lengths for comparison" on both sides.
2. Pass the search value in its "natural", untransformed form and it errors
import bisect
data = sorted(["fig", "kiwi", "apple", "mango"], key=len)
try:
bisect.bisect_left(data, "kiwi", key=len)
except TypeError as e:
print("error:", e)
error: '<' not supported between instances of 'int' and 'str'
Measured: passing the string "kiwi" -- the natural thing to search for in a list of strings -- raises TypeError, because bisect compares it directly against the int lengths produced by key() on each list element. The fix is to pass len("kiwi"), not "kiwi".
3. If the wrong value happens to be the same type, there's no error at all -- just a wrong index
import bisect
events = [{"name": "a", "minute": 5}, {"name": "b", "minute": 12}, {"name": "c", "minute": 40}]
correct = bisect.bisect_left(events, 20, key=lambda e: e["minute"])
print("x already in minutes:", correct)
mistake = bisect.bisect_left(events, 20 * 60, key=lambda e: e["minute"]) # meant minute=20, passed seconds
print("x in seconds, key extracts minutes:", mistake)
x already in minutes: 2
x in seconds, key extracts minutes: 3
Measured: passing 1200 (20 minutes in seconds) where the key function extracts plain minutes doesn't raise anything -- both sides are int, so the comparison just runs and returns a different, wrong insertion index (3 instead of 2). There's no type mismatch to catch here, so this version of the mistake is silent.
Frequently asked questions
Does bisect's key= parameter transform the value you're searching for?
No -- we measured key() being called on every element of the list being searched, but never on x, the search value itself. x must already be in the same form key() would produce.
Why does bisect.bisect_left(data, 'kiwi', key=len) raise TypeError?
Because key=len transforms each list element into its length (an int), and bisect then compares the raw string 'kiwi' directly against those ints -- we measured this raising "'<' not supported between instances of 'int' and 'str'", since bisect never applies len() to the search value.
What happens if the untransformed search value is the same type as the key's output?
No error, but a silently wrong result -- we measured passing seconds (1200) into a list keyed by minutes returning a different, incorrect insertion index than passing the correctly-converted value (20), with nothing flagging the mismatch.
How do you correctly search a bisect list that uses key=?
Convert your search value into the same space the key function produces before passing it -- we measured bisect_left(data, len('kiwi'), key=len) working correctly, versus bisect_left(data, 'kiwi', key=len) raising TypeError.
Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in bisect module: called bisect_left() with key= and compared passing a pre-transformed search value against passing the raw, untransformed one. PlainHeaders is our extension; Python's bisect module is built in.