▷ Bubble sort en Python — explicado paso a paso (con animación) 2026

Bubble sort en Python — explicado paso a paso (con animación)

Bubble sort es el algoritmo de ordenación más sencillo de entender, el primero que enseñan en clase y, a la vez, uno de los más lentos que existen. Si tuvieras que ordenar una fila de personas por altura comparando solo a cada una con la de al lado e intercambiándolas cuando estén descolocadas, eso es exactamente lo que hace bubble sort. Punto. Esa es toda la idea.

Esta entrada cubre el algoritmo en serio: qué hace paso a paso, su implementación en Python, JavaScript y Java, por qué su complejidad es O(n²) (con números reales), cuándo te puede salvar y cuándo no, y la comparativa rápida con los algoritmos que sí se usan en producción.

Si quieres el panorama completo de los 14 algoritmos de ordenación más conocidos y, sobre todo, qué algoritmo usa de verdad cada lenguaje (spoiler: ninguno usa bubble), tienes el pilar del clúster: Algoritmos de ordenación en Python — los 14 explicados.

Bubble sort en una frase

Recorrer la lista comparando elementos vecinos y, si están en el orden equivocado, intercambiarlos. Repetir hasta que no haya intercambios. El número más grande “burbujea” hacia el final en cada pasada — de ahí el nombre.

Aquí lo tienes funcionando sobre 12 valores. La animación va con su contador de comparaciones e intercambios en vivo, para que veas exactamente por qué es lento:

Antes de seguir: qué quieren decir O, Θ y Ω

En la tabla de complejidad vas a ver tres símbolos: O, Θ y Ω. Si no los conoces, parecen lo mismo. No lo son.

  • O(n²) — “O grande”. Es la cota superior: el peor caso. “Como mucho hará del orden de n² operaciones.”
  • Ω(n) — “Omega”. Es la cota inferior: el mejor caso. “Como mínimo hará n operaciones, ni con la lista perfecta puede ir más rápido.”
  • Θ(n²) — “Theta”. Cuando cota superior e inferior coinciden, decimos que el algoritmo es Θ de esa función. Es el caso típico: lo que verás casi siempre.

Bubble sort es Ω(n) (mejor caso con lista ya ordenada, sale en una pasada), Θ(n²) (caso medio), O(n²) (peor caso con lista al revés). Mismo algoritmo, tres velocidades muy distintas según la entrada. Explicación más larga en el pilar del clúster.

Cómo funciona — traza paso a paso

Vamos a ordenar una lista pequeña, [3, 1, 4, 1, 5], a mano para que veas cada movimiento.

Pasada 1:
– Comparo 3 y 1. ¿3 > 1? Sí → intercambio → [1, 3, 4, 1, 5].
– Comparo 3 y 4. ¿3 > 4? No → sigo → [1, 3, 4, 1, 5].
– Comparo 4 y 1. ¿4 > 1? Sí → intercambio → [1, 3, 1, 4, 5].
– Comparo 4 y 5. ¿4 > 5? No → sigo → [1, 3, 1, 4, 5].

El 5 ya está en su sitio al final. Esto es la clave: cada pasada deja al menos un elemento más en su posición correcta, por el lado derecho.

Pasada 2:
1, 3 → no toca.
3, 1 → intercambio → [1, 1, 3, 4, 5].
3, 4 → no toca.

(El 4 ya está bloqueado al final, así que ahorramos esa comparación.)

Pasada 3:
1, 1 → no toca.
1, 3 → no toca.

No hubo intercambios en esta pasada → la lista está ordenada. Termina.

Total: 9 comparaciones, 4 intercambios. Para 5 elementos. Imagínate con un millón.

📥 Llévate el cheatsheet de Optimización Python (gratis)

PDF de 8 páginas con los patrones que más rendimiento te dan en Python: medir antes de optimizar, complejidad real, estructuras adecuadas, evitar trabajo repetido, profiling y cuándo paralelizar. Para tener al lado cuando algo va lento.

Sin spam. Te apuntas a la lista, descargas el cheatsheet y recibes contenido de rendimiento en Python cada semana.

Implementación en Python

La versión más limpia, con el truco de salir antes si la pasada fue “limpia”:

def ordenar(lista: list[int]) -> list[int]:
    n = len(lista)
    for i in range(n - 1):
        intercambio = False
        for j in range(n - 1 - i):
            if lista[j] > lista[j + 1]:
                lista[j], lista[j + 1] = lista[j + 1], lista[j]
                intercambio = True
        if not intercambio:
            break
    return lista


if __name__ == "__main__":
    print(ordenar([3, 1, 4, 1, 5]))  # [1, 1, 3, 4, 5]

Tres detalles:

  1. El n - 1 - i del bucle interno: como cada pasada bloquea el mayor al final, no hace falta volver a tocarlo.
  2. El intercambio booleano: si una pasada termina sin intercambios, la lista ya está ordenada y salimos antes. Esto es lo que da el mejor caso O(n).
  3. Devuelvo la lista por comodidad, pero el método ordena in-place (modifica la original).

En entrevistas técnicas, mencionar explícitamente esa optimización de “salir antes si no hay intercambios” suma puntos. Demuestra que entiendes lo que hace cada línea, no solo el algoritmo.

Implementación en JavaScript

function ordenar(lista) {
  const n = lista.length;
  for (let i = 0; i < n - 1; i++) {
    let intercambio = false;
    for (let j = 0; j < n - 1 - i; j++) {
      if (lista[j] > lista[j + 1]) {
        [lista[j], lista[j + 1]] = [lista[j + 1], lista[j]];
        intercambio = true;
      }
    }
    if (!intercambio) break;
  }
  return lista;
}

console.log(ordenar([3, 1, 4, 1, 5])); // [1, 1, 3, 4, 5]

La estructura es idéntica. Lo único distinto: el intercambio usa array destructuring en lugar del tuple swapping de Python.

Implementación en Java

public static void ordenar(int[] lista) {
    int n = lista.length;
    for (int i = 0; i < n - 1; i++) {
        boolean intercambio = false;
        for (int j = 0; j < n - 1 - i; j++) {
            if (lista[j] > lista[j + 1]) {
                int tmp = lista[j];
                lista[j] = lista[j + 1];
                lista[j + 1] = tmp;
                intercambio = true;
            }
        }
        if (!intercambio) break;
    }
}

Java necesita la variable temporal para el intercambio (no tiene tuple swap nativo). El resto, idéntico.

Complejidad — por qué es O(n²)

MétricaMejor casoCaso medioPeor caso
TiempoΩ(n)Θ(n²)O(n²)
MemoriaO(1)O(1)O(1)
Estable

¿De dónde sale el O(n²)? En cada pasada haces hasta n comparaciones, y haces hasta n pasadas. Multiplicas: n × n = n². Para n = 1.000, hablamos de hasta un millón de comparaciones. Para n = 1.000.000, un billón. Esto te da la diferencia entre tardar 1 segundo y tardar 10 horas en una operación rutinaria.

El mejor caso O(n) solo aparece si la lista ya está ordenada de entrada: una sola pasada confirma que no hay nada que intercambiar y sales. Por eso bubble se defiende sorprendentemente bien con listas casi ordenadas, aunque sigue sin ser la mejor opción ahí (insertion lo bate).

Por qué es estable: dos elementos iguales nunca se intercambian (la condición es >, estricta). Así, el orden relativo de los duplicados se conserva. Importante cuando ordenas, por ejemplo, una lista de objetos por una clave y quieres que el orden original se respete en los empates.

Ver en acción — bubble sort sobre 10 listas fijas

Toda la comparativa de este clúster usa las mismas 10 listas fijas (con n=15) para que cualquier algoritmo se pueda cruzar honestamente con cualquier otro. Estas son:

#NombreQué es
1ordenada1, 2, 3, ..., 15
2inversa15, 14, ..., 1
3casi_ordenada1..15 con un par de swaps
4muy_desordenadaPatrón min-max alternado [1, 15, 2, 14, 3, 13, ...]
5duplicadosEnteros del rango [1..5] repetidos
6-10aleatoria_s1 .. aleatoria_s55 permutaciones aleatorias deterministas (semillas 1..5)

Si quieres ver los valores literales de cada lista, hay un comando:

from sortlab import listas_estandar
for nombre, lista in listas_estandar(n=15).items():
    print(f"{nombre:<16} = {lista}")

Y para correr bubble sobre las 10 y volcar la tabla:

from sortlab import bench_estandar, formato_tabla
print(formato_tabla(bench_estandar("bubble", n=15)))

Salida real (tiempo es el mejor de 5 mediciones; Caso y Θ observada los deduce la propia librería comparando con la teoría):

Lista            Comp.  Interc.  Escr.  Tiempo (ms)   Caso  Θ observada
---------------  -----  -------  -----  -----------  -----  -----------
ordenada         14           0      0        0.001  Mejor         Θ(n)
inversa          105        105    210        0.018   Peor        Θ(n²)
casi_ordenada    95          17     34        0.008   Peor        Θ(n²)
muy_desordenada  84          49     98        0.011   Peor        Θ(n²)
duplicados       84          39     78        0.010   Peor        Θ(n²)
aleatoria_s1     104         62    124        0.014   Peor        Θ(n²)
aleatoria_s2     104         54    108        0.013   Peor        Θ(n²)
aleatoria_s3     102         62    124        0.013   Peor        Θ(n²)
aleatoria_s4     102         69    138        0.014   Peor        Θ(n²)
aleatoria_s5     95          50    100        0.011   Peor        Θ(n²)

Cómo se lee esto:

  1. ordenada es el ÚNICO caso de “Mejor” — 14 comparaciones y cero intercambios. El truco del booleano “si no hubo intercambios, sal” se ve clarísimo: una pasada y bubble termina. Para n=15 esperaríamos n−1 = 14, y eso es exactamente lo que sale: Θ(n) cuadra al milímetro.
  2. Todas las demás son “Peor”. Sí, también las aleatorias. La lectura honesta es que bubble vive cerca del peor caso cualquier momento en que la lista no esté ya ordenada. Las comparaciones oscilan entre 84 y 105 (el máximo teórico es n(n-1)/2 = 105). Esto explica por qué bubble se considera inútil en producción.
  3. casi_ordenada sigue siendo Peor, aunque con muchos menos intercambios (17 frente a 105 de inversa). Bubble compara todo siempre que haya UNA inversión, aunque la mayoría de la lista esté bien.
  4. muy_desordenada y duplicados quedan en 84 comparaciones: el patrón min-max y los duplicados hacen que algunas pasadas salgan limpias antes y la optimización del booleano se active alguna vez. Aún así, sigue siendo Peor.

¿Y cómo escala con n grande? Sobre una lista aleatoria (aleatoria_s1, igual estructura, distintos tamaños):

nComparacionesTiempo
1004.9400,5 ms
1.000499.14952,6 ms
10.00049.986.2225.364 ms (5,4 SEGUNDOS)

Multiplicas n por 10 → el trabajo se multiplica por casi 100. Eso es Θ(n²) traducido a una experiencia humana: para 10.000 elementos, bubble tarda 5 segundos. Quicksort, sobre la misma entrada, tarda 16 milisegundos (335 veces más rápido).

Reproducirlo sin instalar nada

sortlab vive privado en el Python profesional: medir, optimizar y escalar (con todas las versiones explicadas, profiling y la justificación de cada número). Pero la idea es sencilla: cualquier algoritmo se puede instrumentar a mano. Esta es la versión instrumentada de bubble — copia, pega y ejecuta:

def bubble_medido(lista_original: list[int]) -> dict:
    arr = list(lista_original)
    comparaciones = 0
    intercambios = 0
    n = len(arr)
    for i in range(n - 1):
        intercambio = False
        for j in range(n - 1 - i):
            comparaciones += 1
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                intercambios += 1
                intercambio = True
        if not intercambio:
            break
    return {
        "ordenada": arr,
        "comparaciones": comparaciones,
        "intercambios": intercambios,
    }


# Las 5 listas con forma:
listas = {
    "ordenada":         list(range(1, 16)),
    "inversa":          list(range(15, 0, -1)),
    "casi_ordenada":    [1, 11, 3, 4, 5, 6, 7, 8, 9, 10, 2, 12, 13, 14, 15],
    "muy_desordenada":  [1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8],
    "duplicados":       [1, 1, 3, 2, 2, 2, 1, 5, 1, 5, 4, 1, 1, 1, 2],
}
for nombre, lista in listas.items():
    r = bubble_medido(lista)
    print(f"{nombre:<16}  comp={r['comparaciones']:>3}  swap={r['intercambios']:>3}")

Salida (idéntica a la columna “Comp.” y “Interc.” de la tabla de arriba para esas 5 listas).

Mitos que circulan sobre bubble sort

  • “Bubble sort es el algoritmo más sencillo.” Discutible. Insertion sort es igual de sencillo o más, y tiene mejor rendimiento. La razón por la que bubble se enseña primero es histórica, no técnica.
  • “Es bueno para listas casi ordenadas.” No del todo. Es aceptable (sigue comparando todo, aunque haga pocos intercambios), pero insertion lo bate por goleada en ese caso.
  • “Si tienes pocos elementos da igual qué algoritmo uses.” Cierto solo si “pocos” significa menos de ~15-20. A partir de ahí, la diferencia se nota.

¿Y en la stdlib de Python?

No. Ningún lenguaje moderno lleva bubble sort en su biblioteca estándar. Python usa Timsort (list.sort, sorted); C++ usa Introsort (std::sort); Java usa Timsort para objetos y Dual-Pivot Quicksort para primitivos. Bubble se queda en los manuales como ejercicio pedagógico.

Trivia algorítmica

El nombre “bubble sort” apareció en publicaciones de los años 60. Donald Knuth lo trató con la frase clásica: “the bubble sort seems to have nothing to recommend it, except a catchy name and the fact that it leads to some interesting theoretical problems”. Es decir: el nombre es lo mejor que tiene.

Cuándo usar bubble (y cuándo no)

Sí:

  • Para enseñar el concepto de ordenación, comparación e intercambio. Es pedagógicamente perfecto.
  • Listas muy cortas (menos de 10-15 elementos) donde el overhead de algoritmos más sofisticados no compensa.
  • Listas casi ordenadas donde solo unos pocos elementos están fuera de sitio, y solo si no tienes insertion sort a mano.

No:

  • Producción seria. Casi cualquier otro algoritmo lo bate. Si lo ves en un código corporativo, es bandera roja.
  • Listas grandes. A partir de unos miles de elementos se vuelve doloroso.
  • Datos en streaming. No está diseñado para procesarse por trozos.

Comparativa rápida

Para que te hagas una idea del lugar que ocupa bubble dentro del clúster:

AlgoritmoMejorPeorMemoriaEstableVentaja
BubbleΩ(n)O(n²)O(1)Conceptualmente trivial
InsertionΩ(n)O(n²)O(1)Más rápido que bubble en casi todos los casos
SelectionΩ(n²)O(n²)O(1)NoMínimo número de intercambios
QuicksortΩ(n log n)O(n²)O(log n)NoSúper rápido en la práctica
Merge sortΩ(n log n)O(n log n)O(n)Garantiza O(n log n) siempre

En la práctica: insertion sort hace lo mismo que bubble pero mejor en casi cualquier escenario. No hay razón práctica para preferir bubble sobre insertion, salvo enseñarlo. Y ya está.

FAQ rápida

¿Por qué se llama “bubble” sort?
Porque los valores más grandes “burbujean” hacia el final de la lista en cada pasada, como burbujas subiendo en el agua.

¿Existe un bubble sort estable?
Sí, el estándar ya lo es. Solo intercambia cuando un elemento es estrictamente mayor que el siguiente, así que los iguales mantienen su orden original.

¿Cuál es el “bubble sort optimizado”?
La versión con la salida temprana (el intercambio booleano de los ejemplos de arriba) y el n - 1 - i para no tocar elementos ya bloqueados. Aún así es O(n²); no hay forma de salir de ahí sin cambiar de algoritmo.

¿Bubble sort es recursivo?
La versión clásica es iterativa. Puedes escribir una recursiva, pero solo añade complejidad sin ventaja.

¿Sirve bubble para ordenar strings?
Sí, cualquier comparable. Cambia > por la comparación que toque. En Python, > ya funciona entre strings (orden lexicográfico).

¿Te ha valido esto?

Si te ha resultado útil, llévate el cheatsheet de Optimización Python en PDF — 8 páginas con cómo medir, cuándo importa la complejidad, qué estructura usar y profiling práctico. Para tener al lado cuando algo va lento. Gratis.

Sin spam. Email + cheatsheet de rendimiento + un correo por semana sobre medir y optimizar Python.

En resumen

Bubble sort es el “Hola, Mundo” de los algoritmos de ordenación. Sirve para entender el concepto, pero no para producir. Cualquier lenguaje moderno usa algo más sofisticado (Timsort en Python, Introsort en C++…); ninguno usa bubble. Conocerlo es importante; usarlo en serio, no.

Si quieres aprender a medir cuánto tarda tu código de verdad —no solo a recitar Big-O— y a elegir la estructura correcta para cada problema, ese es el camino del curso de El Pythonista. Y si esto te enganchó, te dejo abajo el cheatsheet de optimización.

¿Quieres aprender Python en orden, no a saltos?

Esto que has leído es solo una pieza. En El Pythonista lo verás todo encadenado: 11 módulos, 37+ horas de vídeo, 734 actividades y un proyecto real (MovieTracker) que crece contigo desde la primera variable hasta el deploy a producción.

Ver el curso completo →

37+ horas · 734 actividades · Proyecto real · Acceso de por vida · 14 días de garantía

Compartir

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicar un comentario