Ángela Sarrión Belmonte escribió veinte líneas de Python para la biblioteca del instituto donde da clase, en Cáceres. El programa cruza los 12.400 préstamos de los últimos cuatro cursos contra los 8.900 ISBN que siguen en el catálogo y saca los que apuntan a un libro que ya no está. Da el resultado correcto y tarda 0,53 segundos.
Esa misma respuesta cabe en 0,4 milisegundos cambiando una línea, y la línea no está dentro del bucle. Los fundamentos no son la sintaxis que comparten los lenguajes, son los precios que comparten.
Medio segundo no molesta a nadie. Lo que enseña es la cuenta que tiene detrás.
110.360.000 comparaciones para 12.400 preguntas
La versión que sale sola es esta, con el catálogo cargado en una lista:
- catalogo = [linea.split(";")[0] for linea in open("catalogo.csv")]
- bajas = [p for p in prestamos if p.isbn not in catalogo]
El operador in sobre una lista la recorre hasta encontrar lo que busca. Con 12.400 préstamos y 8.900 ISBN, el peor caso son 110.360.000 comparaciones para contestar 12.400 preguntas. La línea que lo cambia va antes del bucle y dice catalogo = set(catalogo): con un conjunto, cada búsqueda deja de recorrer nada.
Medido en un portátil en septiembre de 2026, la lista tarda 0,53 segundos y el conjunto 0,4 milisegundos. Multiplica los dos ficheros por diez y el medio segundo pasa a 53 segundos, porque el trabajo crece con el producto de los dos; el conjunto pasa a 4 milisegundos, porque crece con uno solo.
De ahí sale la frase que se lleva antes que ninguna otra: una búsqueda en un diccionario cuesta lo mismo con diez claves que con diez millones. Un conjunto cobra igual, porque por dentro es la misma tabla sin los valores. Y su reverso, que es la mitad que se olvida: in sobre una lista recorre y sobre un conjunto no.
El segundo precio está en la forma del bucle. Ángela tenía un bucle metido dentro de otro sin haberlo escrito, porque in era el de dentro. Cuando los dos se escriben, la cuenta se ve: dos bucles anidados sobre la misma lista son n al cuadrado, y sobre el catálogo eso son 79.210.000 vueltas.
Ordenar se paga una vez y buscar se paga cada vez
La otra mitad de la cuenta sale cuando Ángela quiere los 300 préstamos pendientes más antiguos. La forma directa recorre los 4.100 pendientes buscando el más viejo, lo aparta y repite la vuelta 300 veces: 1.230.000 comparaciones, y esa cifra crece con los dos números a la vez.
sorted(pendientes, key=lambda p: p.fecha)[:300] coloca la lista entera una vez y después corta. Ordenar 4.100 elementos cuesta del orden de 49.200 comparaciones, unas veinticinco veces menos, y deja los datos listos para la siguiente pregunta sobre la misma lista. Ordenar cuesta n log n y se paga una sola vez, mientras que buscar sin ordenar se paga en cada pregunta.
Casi todas las respuestas con nombre tienen esa forma: gastar una vez en colocar los datos para que las preguntas de después salgan baratas. Y en una revisión de código la diferencia entre decir que algo va lento y decir que hace 110.360.000 comparaciones es que la segunda frase se puede comprobar delante de quien la discute.
El pseudocódigo del examen no lleva precios
Aquí se para casi todo el mundo que llega con el módulo aprobado, y no es por falta de teoría. Un algoritmo se escribe primero en pseudocódigo, y en pseudocódigo buscar en una lista y buscar en un diccionario ocupan lo mismo, una línea. La notación con la que se examina el tema es justo la que no puede mostrar de qué va.
La corrección va por el mismo sitio. Una prueba de dos horas se corrige leyendo: si el método devuelve la lista ordenada, está bien, y nadie le pone un cronómetro. Se sale sabiendo definir una tabla hash y sin haber elegido ninguna nunca, que son dos cosas distintas y solo la segunda se parece a programar.
La señal de haber salido de ahí es pequeña y se reconoce sola: delante de un fichero de datos, la primera pregunta deja de ser cómo se recorre y pasa a ser cuántas veces. Las estructuras de datos son esa pregunta entera, y los algoritmos son las respuestas que ya tienen nombre puesto por otra persona.
Dos nombres para la misma lista
El tercer precio no se paga en tiempo sino en una tarde entera. Ángela quiso guardar los préstamos sin devolver antes de ordenarlos por fecha:
- pendientes = prestamos
- pendientes.sort(key=lambda p: p.fecha)
Después de esas dos líneas, prestamos también está ordenado. La misma lista con dos nombres sigue siendo una sola lista: el signo igual no copia nada, engancha un segundo nombre al mismo sitio de memoria. La copia hay que pedirla, con list(prestamos) o con sorted(), que devuelve una lista nueva y deja quieta la de partida.
Este fallo no da error. Da un informe correcto y otro, dos funciones más abajo, con las filas en un orden que nadie pidió, así que se busca una hora en el sitio equivocado. Reconocer esa forma de avería, la que no levanta ninguna excepción, es la mitad de la depuración.
El orden
Cada peldaño es lo que hace que el siguiente signifique algo
La escalera se sube al revés casi siempre, porque un curso empieza en el segundo peldaño y del primero no dice nada.
- 01Medir algo tuyo una vezUn cronómetro sobre un fichero que ya funciona. Nada de lo que hay encima se sostiene sin esto, y no lo pide ningún examen.
- 02Poner precio a cada formaUna lista se recorre, un diccionario se direcciona. Los nombres pueden esperar; los precios no.
- 03Chocar dos veces con el mismo problemaUn patrón leído antes de la segunda vez es una solución sin ninguna avería debajo.
- 04Volver a un fichero tuyo de hace ocho mesesLas costumbres del código mantenible no se entienden hasta que no se entiende el propio código.
No dice cuánto dura un peldaño. El primero cabe en una tarde, no lo programa ningún temario y por eso hay que provocarlo.
La pila tiene fondo, y está en 1000
Cuando una función llama a otra, la primera no desaparece: se queda esperando con sus variables en un sitio llamado pila. Un marco de la pila es exactamente eso, una función en pausa. Por eso un error trae una lista de líneas y no una: es la pila de funciones abiertas en ese instante.
Y tiene fondo. El límite de recursión de Python son 1000 llamadas abiertas por defecto, y pasarse no cuelga el ordenador, levanta un RecursionError con su línea y su mensaje. Recorrer un árbol de carpetas llamándose a sí misma es correcto y nunca llega a 1000; recorrer los 12.400 préstamos de la misma forma se estrella siempre en el mismo sitio.
La pila se lee de abajo arriba, y lo de abajo del todo suele ser código de otro. Lo que hay debajo de todo esto, el intérprete y la memoria que reparte, está en cómo se ejecuta el código.
Qué abrir en 2026 para poner el número
timeit lleva en la biblioteca estándar de Python desde 2003, no se instala, no pide cuenta y no cuesta nada, que en este tema es lo normal y no una ventaja. Sustituye a poner un cronómetro alrededor del fichero entero, que en un script de veinte líneas mide sobre todo el arranque del intérprete. timeit ejecuta el fragmento muchas veces y descuenta el arranque del intérprete, y ese descuento es la razón de usarlo.
pdb viene en el mismo sitio y responde a la otra pregunta. Sustituye a meter un print y volver a ejecutar el fichero, que obliga a acertar de antemano qué variable interesaba. Con breakpoint() en la línea sospechosa, el programa para ahí y deja mirar lo que sea: p prestamos[0] imprime, w enseña la pila entera y n avanza una línea.
Ninguno de los dos sale en un temario ni tiene manual en PDF, y los dos están ya instalados en el ordenador donde estás leyendo esto.
El orden que funciona, y lo que cuesta al revés
El primer peldaño es una medición y no una definición, y es el único que no programa nadie: hay que provocarlo. Un fichero propio, un cronómetro y una cifra que no cuadra dejan una marca que ninguna clase deja.
Al revés se paga así. Los patrones de diseño leídos antes de haberse encontrado el problema dos veces son vocabulario suelto, y se acaban aplicando donde no hacen falta, con lo cual el programa crece sin resolver nada. El código mantenible es peor de aprender en frío: sus costumbres solo tienen sentido después de haber abierto un fichero propio a los ocho meses y no reconocerlo.
Los ocho apartados de aquí siguen valiendo cuando cambias de sintaxis, y esa es la diferencia con el tema de al lado. Guardar y recuperar los datos con la luz cortándose a mitad de una escritura no cabe en ninguno de los ocho, y por eso vive en bases de datos; sacar el programa de tu máquina tampoco, y está en la puesta en producción.
Los bloques básicos son el peldaño que casi todo el mundo trae ya aprobado, y aun así vale la pena volver a ellos con el precio en la mano, porque de las cinco construcciones solo una se cobra por vuelta. La programación orientada a objetos repite ese mismo trato a otra escala, y entra mucho antes cuando el trato ya se ha visto dentro de un bucle.
Queda la pregunta con la que llega casi todo el mundo a este punto de la programación: en qué sintaxis se practica esto. La cuenta de Ángela sale igual en Java y en PHP, y lo que cambia es todo lo que rodea a la sintaxis, que es el tema de los lenguajes de programación.