El módulo bisect encuentra posiciones en listas ordenadas mediante búsqueda binaria. Es útil para consultas frecuentes en secuencias moderadas, pero no convierte la inserción en una operación barata.

Elegir el lado de inserción

from bisect import bisect_left, bisect_right, insort

notas = [5, 7, 7, 9]
assert bisect_left(notas, 7) == 1
assert bisect_right(notas, 7) == 3

insort(notas, 8)
assert notas == [5, 7, 7, 8, 9]

bisect_left apunta antes de los iguales y bisect_right después. La elección importa en intervalos, paginación y desempates. La secuencia debe conservar el mismo orden.

Buscar objetos por clave

from bisect import bisect_left

productos = [
    {"nombre": "Básico", "precio": 50},
    {"nombre": "Pro", "precio": 120},
]

posicion = bisect_left(productos, 100, key=lambda item: item["precio"])
assert posicion == 1

El valor buscado representa la clave, por eso es 100. Si calcularla es caro y la búsqueda se repite, considera una lista paralela de claves.

Convertir el punto de inserción en búsqueda

Las funciones devuelven un punto de inserción, no confirman que el elemento exista. Para localizar una ocurrencia, calcula el índice y valida el valor:

from bisect import bisect_left


def localizar(valores: list[int], objetivo: int) -> int:
    indice = bisect_left(valores, objetivo)
    if indice != len(valores) and valores[indice] == objetivo:
        return indice
    raise ValueError(f"{objetivo} no encontrado")

La comprobación del límite es necesaria porque un objetivo mayor que todos produce len(valores). El mismo patrón implementa pertenencia, el primer elemento mayor o igual y otras consultas sin escribir la búsqueda binaria manualmente.

bisect_left(valores, x) divide la lista: antes del índice todos son menores que x; desde el índice son mayores o iguales. bisect_right() incluye los iguales en la región izquierda. Pensar en estas garantías evita errores con intervalos.

Consultar rangos con dos límites

Dos puntos delimitan los valores de un intervalo cerrado:

from bisect import bisect_left, bisect_right


def entre(valores: list[int], minimo: int, maximo: int) -> list[int]:
    inicio = bisect_left(valores, minimo)
    fin = bisect_right(valores, maximo)
    return valores[inicio:fin]


assert entre([2, 4, 4, 7, 9, 12], 4, 9) == [4, 4, 7, 9]

Encontrar límites cuesta O(log n), pero crear el slice consume tiempo y memoria proporcionales al resultado. Si solo necesitas contar, usa fin - inicio. Para iterar sin copiar, recorre índices o usa itertools.islice.

Los límites abiertos y cerrados surgen al combinar variantes. Para [minimo, maximo), usa bisect_left en ambos extremos. Para (minimo, maximo], usa bisect_right en ambos.

Insertar registros con key

insort_left() e insort_right() también aceptan key. La clave se aplica a los elementos y al registro nuevo durante la búsqueda. A diferencia de una consulta con bisect_left(), pasa el registro completo:

from bisect import insort_right

eventos = [
    {"instante": 10, "nombre": "inicio"},
    {"instante": 20, "nombre": "fin"},
]

insort_right(
    eventos,
    {"instante": 20, "nombre": "auditoría"},
    key=lambda evento: evento["instante"],
)

Con claves iguales, insort_right coloca el nuevo registro después de los existentes. Eso no ofrece estabilidad general si otro código reorganiza la colección. Cuando el desempate importa, usa una clave compuesta como (instante, secuencia).

El parámetro key apareció en Python 3.10. Si una biblioteca admite versiones anteriores, conserva claves paralelas o elige otra estructura. Comprueba la versión mínima real antes de publicar código reutilizable.

Entender el coste completo

La búsqueda examina aproximadamente log2(n) posiciones. Sin embargo, una lista es un array contiguo de referencias. Insertar en medio desplaza elementos, así que domina O(n). Es una buena elección cuando las lecturas superan ampliamente las inserciones, pero puede ser un cuello de botella con miles de cambios intermedios.

Ordenar una vez con sorted() suele ser mejor si todos los datos llegan en lote. Insertar cada uno de n elementos con insort puede aproximarse a trabajo cuadrático. Ordenar toda la lista tras cada elemento también desperdicia recursos. Evalúa el patrón real, la proporción de lecturas e inserciones y el tamaño máximo.

Si solo necesitas extraer repetidamente el menor o mayor elemento, considera heapq. Un heap no conserva todos los valores ordenados para rangos, pero insertar y retirar prioridades cuesta O(log n). Para concurrencia y persistencia, un índice de base de datos suele ofrecer garantías más adecuadas.

Evitar recalcular claves costosas

En búsquedas repetidas, bisect puede llamar key sobre los mismos elementos y descartar los resultados. Si la clave hace parsing o normalización, conserva una lista sincronizada:

from bisect import bisect_left

nombres = ["Ana", "Érica", "Juan"]
claves = [nombre.casefold() for nombre in nombres]

nuevo = "bruno"
indice = bisect_left(claves, nuevo.casefold())
claves.insert(indice, nuevo.casefold())
nombres.insert(indice, nuevo)

Ambas listas deben actualizarse juntas. Encapsúlalas para impedir que alguien modifique solo una. functools.cache puede ayudar con objetos inmutables repetidos, pero una cache ilimitada tiene su propio coste de memoria.

Para texto humano, casefold() resuelve diferencias básicas de mayúsculas, no todas las reglas de collation. Si el orden lingüístico es un requisito, usa una herramienta apropiada y genera claves de manera consistente.

Mantener invariantes durante mutaciones

Cambiar un campo usado en la ordenación después de insertar el objeto rompe silenciosamente la precondición. Retira y vuelve a insertar el registro, o utiliza valores inmutables. Tampoco mezcles tipos sin un orden total compatible.

Las funciones no son thread-safe cuando varias threads operan sobre la misma secuencia. Una mutación concurrente puede producir resultados indefinidos y dejar la lista desordenada. Protege la secuencia y las operaciones compuestas con el mismo lock, o asigna los cambios a un único propietario.

Incluso en una thread, “buscar y después insertar” es una operación compuesta. Una mutación entre ambos pasos invalida el índice. insort los reúne en una llamada cómoda, pero sigue necesitando sincronización externa con concurrencia.

Decidir cuándo usar bisect

Usa bisect si la secuencia ya está ordenada, las comparaciones comparten una clave consistente y las consultas superan las inserciones. Encaja en tablas de umbrales, historiales temporales pequeños, selección de rangos, percentiles sobre datos ordenados y configuración.

Prueba una lista vacía, objetivos antes del primero y después del último, duplicados y límites inclusivos. Comprueba también que la secuencia continúa ordenada tras cada operación. Esos casos documentan qué lado de los valores iguales forma parte del contrato.

Una aplicación frecuente convierte una medida continua en categoría. Conserva límites superiores ordenados, usa bisect_right y selecciona una etiqueta con el índice. Define el comportamiento en un límite exacto y fuera del rango. Así las bandas de precios, puntuaciones y alertas quedan compactas y auditables.

Para datos inmutables consultados por muchas peticiones, construye y valida la secuencia una vez al iniciar. No llames sorted() antes de cada consulta, pues convierte una búsqueda logarítmica en trabajo dominado por O(n log n). Si los límites vienen de una fuente externa, valida el orden y rechaza claramente una configuración incorrecta.

No apliques bisect directamente a un iterador genérico. La búsqueda necesita acceso aleatorio por índice y longitud conocida. Convertir un generador grande en lista puede destruir su ventaja de memoria, por lo que conviene replantear el algoritmo.

Al medir rendimiento, incluye tamaños representativos y mantenimiento de la estructura. Cronometrar solo bisect_left omite desplazamientos, generación de claves, locks y copias de slices, que con frecuencia dominan la aplicación real.

Mantén pura y determinista la regla de comparación. Una función key que consulta estado global cambiante, hora actual o datos externos puede tomar decisiones incoherentes durante una sola búsqueda. Calcula ese estado antes y utiliza registros estables. Las invariantes claras importan más que ahorrar unas líneas: documenta cómo se colocan duplicados, quién puede modificar la colección y si los consumidores reciben una copia o la lista original.

Compara con la guía para ordenar listas en Python. Con muchas inserciones intermedias, una base de datos, heap o estructura especializada puede ser mejor.

La documentación oficial de bisect, consultada el 28 de julio de 2026, cubre precondiciones, key, seguridad entre threads y recetas. La búsqueda cuesta O(log n), pero insertar en una lista sigue costando O(n).