Bucle for en Python: guía completa con range, enumerate y zip

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 18 min de lectura
Τὸ καθόλου ἐκ τῶν καθ' ἕκαστα
«Lo universal surge de examinar cada particular»
Aristóteles de Estagira · Filósofo griego · 384 a.C. – 322 a.C.
Aristóteles fue el primer gran sistematizador del conocimiento occidental. Catalogó más de 500 especies animales en su Historia Animalium, clasificó las formas de gobierno en la Política, y enumeró las categorías del ser en la Metafísica. En la práctica, Aristóteles pasó su vida entera ejecutando for sobre el universo conocido. Su método era siempre el mismo: observar cada caso particular, uno a uno, para inferir la ley general. El bucle for de Python es, en ese sentido, el instrumento filosófico más aristotélico que existe. No proclama verdades a priori — recorre cada elemento, lo procesa, y construye comprensión desde lo concreto. Si Aristóteles hubiera tenido Python, habría escrito for especie in universo_animal: catalogar(especie) y habría terminado antes.

El for de Python no se parece al for de C o Java. No trabaja con índices por defecto, no tiene condición de parada, no tiene incremento manual. Lo que tiene es algo mejor: itera directamente sobre los elementos de cualquier objeto iterable. Menos código, más intención, menos errores. Una vez que lo interiorizas, el for i = 0; i < n; i++ de otros lenguajes parece un trabalenguas innecesario.

🔁 El bucle for: iterar sobre cualquier secuencia

La sintaxis es for elemento in iterable:. En cada iteración, elemento toma el valor del siguiente ítem de iterable. No hay índice, no hay condición, no hay incremento. Python se encarga de todo.

# Recorrer una lista
frutas = ["manzana", "pera", "naranja", "uva"]
for fruta in frutas:
    print(fruta)
# manzana
# pera
# naranja
# uva

# Recorrer un string (carácter a carácter)
for letra in "Python":
    print(letra, end=" ")
# P y t h o n

# Recorrer una tupla
coordenadas = [(0, 0), (1, 2), (3, 4)]
for punto in coordenadas:
    x, y = punto          # desempaquetado
    print(f"x={x}, y={y}")

# Recorrer un set (sin garantía de orden)
colores = {"rojo", "verde", "azul"}
for color in colores:
    print(color)          # orden impredecible

# Recorrer los caracteres de un string buscando vocales
vocales = set("aeiouáéíóú")
texto = "Aristóteles"
encontradas = [c for c in texto.lower() if c in vocales]
print(encontradas)    # ['a', 'i', 'ó', 'e', 'e']
Grupo de niños en fila pisando uno a uno los peldaños de una escalera de agilidad sobre un campo de hierba
Cada niño avanza peldaño a peldaño sin saltarse ninguno — exactamente lo que hace el bucle for: procesar cada elemento de la secuencia en orden, uno a uno, hasta el último. Fuente: Pexels (licencia libre).

📏 range(): repetir N veces con control total

Cuando necesitas repetir algo un número determinado de veces —o generar una secuencia numérica— usas range(). Es perezoso: no crea la lista en memoria, genera cada número cuando el bucle lo necesita.

# range(fin): de 0 a fin-1
for i in range(5):
    print(i)          # 0 1 2 3 4

# range(inicio, fin): de inicio a fin-1
for i in range(1, 6):
    print(i)          # 1 2 3 4 5

# range(inicio, fin, paso): con incremento
for i in range(0, 20, 5):
    print(i)          # 0 5 10 15

# Contar hacia atrás (paso negativo)
for i in range(10, 0, -1):
    print(i)          # 10 9 8 7 ... 1

# range() no incluye nunca el fin:
list(range(5))        # [0, 1, 2, 3, 4]
list(range(1, 5))     # [1, 2, 3, 4]

# range() es perezoso — no crea la lista en memoria:
# range(1_000_000) ocupa 48 bytes constantes
# list(range(1_000_000)) ocuparía ~8 MB
💡 Paso flotante: range() solo trabaja con enteros. Si necesitas iterar con paso decimal, usa numpy.arange(0, 1, 0.1) o una comprensión: [i/10 for i in range(10)].
🎲 Dato inútil pero verdadero: Los programadores de todo el mundo nombran su variable de bucle i porque sí. Técnicamente viene de FORTRAN de los años 50, donde las variables I a N eran enteras por defecto. Desde entonces, generaciones enteras de programadores han perpetuado la costumbre sin preguntarse nada. La próxima vez que escribas for i in range(10), recuerda que estás honrando una convención de 1957. Podrías escribir for patata in range(10) y funcionaría exactamente igual. Nadie lo hace. Nadie sabe por qué.

🔢 enumerate(): índice y valor al mismo tiempo

enumerate() devuelve pares (índice, valor) para cada elemento. Es la forma idiomática de Python cuando necesitas el índice y el valor simultáneamente — evita el patrón range(len(...)) que es más verboso y propenso a errores.

frutas = ["manzana", "pera", "naranja", "uva"]

# ❌ Forma no idiomática (viene de C/Java):
for i in range(len(frutas)):
    print(i, frutas[i])

# ✅ Forma idiomática Python:
for i, fruta in enumerate(frutas):
    print(i, fruta)
# 0 manzana
# 1 pera
# 2 naranja
# 3 uva

# Empezar desde 1 en lugar de 0:
for i, fruta in enumerate(frutas, start=1):
    print(f"{i}. {fruta}")
# 1. manzana
# 2. pera
# 3. naranja
# 4. uva

# Caso real: mostrar errores con número de línea
lineas = ["nombre = 'Ana'", "edad = 'veintiocho'", "activo = True"]
for num, linea in enumerate(lineas, start=1):
    if "'" in linea and "=" in linea and not linea.startswith("nombre"):
        print(f"  Línea {num}: posible error de tipo — {linea}")

# enumerate() funciona con cualquier iterable, no solo listas:
for i, char in enumerate("Python"):
    print(f"  [{i}] = '{char}'")
# [0]='P' [1]='y' [2]='t' [3]='h' [4]='o' [5]='n'

🤐 zip(): dos listas en paralelo

zip() combina dos (o más) iterables emparejando sus elementos por posición. Se detiene cuando el más corto se agota. Para el caso opuesto —continuar hasta el más largo—, usa itertools.zip_longest().

nombres = ["Ana", "Luis", "Marta", "Pedro"]
notas   = [8.5, 6.0, 9.2, 4.8]

for nombre, nota in zip(nombres, notas):
    estado = "✅ aprobado" if nota >= 5 else "❌ suspenso"
    print(f"  {nombre:<10} {nota:.1f}  {estado}")
# Ana        8.5  ✅ aprobado
# Luis       6.0  ✅ aprobado
# Marta      9.2  ✅ aprobado
# Pedro      4.8  ❌ suspenso

# zip() con tres listas:
materias  = ["Matemáticas", "Historia", "Inglés"]
max_notas = [10, 10, 10]
for n, nota, maximo in zip(nombres, notas, max_notas):
    porcentaje = nota / maximo * 100
    print(f"  {n}: {nota}/{maximo} ({porcentaje:.0f}%)")

# Crear un diccionario con zip:
claves  = ["nombre", "edad", "ciudad"]
valores = ["Ana", 28, "Madrid"]
perfil = dict(zip(claves, valores))
print(perfil)    # {"nombre": "Ana", "edad": 28, "ciudad": "Madrid"}

# zip_longest: continúa hasta el más largo
from itertools import zip_longest
a = [1, 2, 3]
b = ["x", "y"]
for n, c in zip_longest(a, b, fillvalue="—"):
    print(n, c)
# 1 x
# 2 y
# 3 —

# zip "inverso": desempaquetar lista de tuplas
pares = [("Ana", 28), ("Luis", 34), ("Marta", 22)]
nombres_sueltos, edades_sueltas = zip(*pares)
print(nombres_sueltos)  # ('Ana', 'Luis', 'Marta')
print(edades_sueltas)   # (28, 34, 22)
Un lápiz amarillo y un lápiz azul cruzándose en diagonal sobre un fondo dividido en los mismos dos colores
Dos secuencias independientes —amarilla y azul— que se encuentran en un punto. Exactamente lo que hace zip(): toma dos listas separadas y las une elemento a elemento, como si deslizaras una cremallera. Fuente: Pexels (licencia libre).

🗂️ Iterar diccionarios: keys, values e items

Los diccionarios tienen tres modos de iteración. Por defecto —si haces for k in d— obtienes las claves. Para valores o pares, llamas explícitamente al método correspondiente.

precios = {"café": 1.50, "tostada": 2.20, "zumo": 2.80, "agua": 1.00}

# Iterar sobre claves (modo por defecto):
for producto in precios:
    print(producto)           # café, tostada, zumo, agua

# Iterar sobre valores:
total = 0
for precio in precios.values():
    total += precio
print(f"Total carta: {total:.2f} €")   # 7.50 €

# Iterar sobre pares clave-valor (lo más habitual):
for producto, precio in precios.items():
    print(f"  {producto:<12} {precio:>5.2f} €")

# Ordenar por valor (precio más bajo primero):
for prod, precio in sorted(precios.items(), key=lambda x: x[1]):
    print(f"  {prod}: {precio:.2f} €")

# Filtrar con comprensión de dict:
caros = {k: v for k, v in precios.items() if v > 2.0}
print(caros)    # {"tostada": 2.20, "zumo": 2.80}

# Iterar dos diccionarios a la vez (Python 3.9+):
ayer   = {"manzanas": 10, "peras": 5}
hoy    = {"manzanas": 8,  "peras": 7, "naranjas": 3}
for fruta, stock in hoy.items():
    anterior = ayer.get(fruta, 0)
    diferencia = stock - anterior
    signo = "▲" if diferencia > 0 else "▼" if diferencia < 0 else "="
    print(f"  {fruta}: {stock} ({signo}{abs(diferencia):+d})")

🔲 Bucles anidados

Un bucle anidado es un for dentro de otro. Por cada iteración del bucle exterior, el interior se ejecuta completo. Útil para matrices, combinaciones, o estructuras de datos bidimensionales.

# Tabla de multiplicar básica:
for i in range(1, 4):
    for j in range(1, 4):
        print(f"{i}×{j}={i*j}", end="  ")
    print()          # salto de línea al final de cada fila
# 1×1=1  1×2=2  1×3=3
# 2×1=2  2×2=4  2×3=6
# 3×1=3  3×2=6  3×3=9

# Recorrer una matriz (lista de listas):
matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for fila in matriz:
    for elemento in fila:
        print(elemento, end=" ")
    print()

# Aplanar una matriz con comprensión (más compacto):
plana = [elem for fila in matriz for elem in fila]
print(plana)    # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Combinaciones de dos listas:
colores = ["rojo", "azul"]
tallas  = ["S", "M", "L"]
for color in colores:
    for talla in tallas:
        print(f"{color}-{talla}", end=" ")
# rojo-S rojo-M rojo-L azul-S azul-M azul-L

# Con itertools.product (equivalente, más limpio):
from itertools import product
for color, talla in product(colores, tallas):
    print(f"{color}-{talla}", end=" ")
💡 Rendimiento en anidados: Cada nivel añade complejidad multiplicativa. Dos bucles sobre listas de n elementos = O(n²). Con tres niveles = O(n³). Si el contenido del bucle interior es costoso, considera si puedes reformularlo con itertools.product, matrices NumPy, o una estructura de datos diferente.
Diagrama de flujo del bucle for en Python: ciclo completo con rombo de decisión, bloque de ejecución, flecha de retorno, else opcional y salidas break y continue
El ciclo completo del bucle for: comprobación de elementos disponibles, ejecución del bloque, retorno a la siguiente iteración, salida por fin natural (→ else) o por break. Infografía: Ciberaula.

💎 El else del for: la joya oculta de Python

El for tiene una cláusula else que casi ningún programador que viene de otro lenguaje conoce. El bloque else se ejecuta cuando el bucle termina de forma natural — es decir, sin que se haya ejecutado un break. Si hay un break, el else no corre.

# Patrón clásico: búsqueda limpia sin variable bandera
usuarios = [
    {"id": 1, "nombre": "Ana",   "activo": True},
    {"id": 2, "nombre": "Luis",  "activo": False},
    {"id": 3, "nombre": "Marta", "activo": True},
]

id_buscado = 5

for usuario in usuarios:
    if usuario["id"] == id_buscado:
        print(f"Encontrado: {usuario['nombre']}")
        break
else:
    print(f"No existe ningún usuario con id={id_buscado}.")
# → No existe ningún usuario con id=5.

# Comparación: sin for/else necesitas una bandera booleana:
encontrado = False
for usuario in usuarios:
    if usuario["id"] == id_buscado:
        encontrado = True
        print(f"Encontrado: {usuario['nombre']}")
        break
if not encontrado:
    print("No encontrado.")    # más verboso, misma lógica

# Verificar si una lista tiene algún elemento que cumpla condición:
numeros = [4, 8, 15, 16, 23, 42]
for n in numeros:
    if n % 7 == 0:
        print(f"Primer múltiplo de 7: {n}")
        break
else:
    print("Ningún múltiplo de 7 en la lista.")
# → Ningún múltiplo de 7 en la lista.

# Verificación de número primo:
def es_primo(n):
    if n < 2:
        return False
    for divisor in range(2, int(n**0.5) + 1):
        if n % divisor == 0:
            return False    # tiene divisor → no es primo, no llega al else
    else:
        return True         # llegó al final sin divisores → es primo

print(es_primo(17))    # True
print(es_primo(18))    # False

🐛 Errores clásicos con for

1. Modificar la lista que se está iterando

numeros = [1, 2, 3, 4, 5, 6]

# ❌ Comportamiento indefinido: se saltan elementos
for n in numeros:
    if n % 2 == 0:
        numeros.remove(n)    # modifica la lista durante la iteración
print(numeros)    # [1, 3, 5, 6] ← el 6 no se eliminó

# ✅ Iterar sobre una copia:
for n in numeros[:]:
    if n % 2 == 0:
        numeros.remove(n)

# ✅ Mejor: list comprehension
numeros = [n for n in numeros if n % 2 != 0]

2. Usar range(len(...)) cuando no es necesario

frutas = ["manzana", "pera", "naranja"]

# ❌ Verboso y propenso a errores de índice:
for i in range(len(frutas)):
    print(frutas[i])

# ✅ Iterar directamente:
for fruta in frutas:
    print(fruta)

# ✅ Si necesitas el índice: enumerate()
for i, fruta in enumerate(frutas):
    print(i, fruta)

3. Confundir for con forEach de JavaScript

numeros = [1, 2, 3, 4, 5]

# ❌ Intento de modificar elementos "in place" (no funciona):
for n in numeros:
    n = n * 2    # modifica la variable local, NO el elemento de la lista
print(numeros)    # [1, 2, 3, 4, 5] — sin cambios

# ✅ Para transformar: list comprehension
numeros = [n * 2 for n in numeros]

# ✅ Para modificar in-place: usar índice
for i in range(len(numeros)):
    numeros[i] *= 2

4. Olvidar que zip() se detiene con el más corto

nombres = ["Ana", "Luis", "Marta", "Pedro"]
notas   = [8.5, 6.0]    # solo 2 notas

for nombre, nota in zip(nombres, notas):
    print(nombre, nota)
# Ana 8.5
# Luis 6.0
# Marta y Pedro ← silenciosamente ignorados

# ✅ Si esto es un error, comprueba las longitudes antes:
if len(nombres) != len(notas):
    raise ValueError(f"Listas de longitud diferente: {len(nombres)} vs {len(notas)}")
Ficha de referencia rápida del bucle for en Python: range con tres formas, enumerate, zip, sorted, reversed y dict.items con ejemplos de código
Referencia rápida: range() en sus tres formas, enumerate(), zip() con zip_longest, sorted()/reversed(), y las tres formas de iterar diccionarios. Ficha de referencia: Ciberaula.

✅ Resumen y próximos pasos

El for de Python itera directamente sobre los elementos de cualquier secuencia — lista, string, tupla, dict, set, generator. range() genera secuencias numéricas sin crear la lista en memoria. enumerate() da índice y valor simultáneamente. zip() une dos o más listas por posición. Y el else del for se ejecuta solo si no hubo break — patrón limpio para búsquedas sin variable bandera.

La siguiente lección: el bucle while — cómo repetir código mientras se cumpla una condición dinámica, y los patrones while True con break para menús e interacción con el usuario.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Bucle for en Python: guía completa con range, enumerate y zip

Las dudas más comunes respondidas de forma clara y directa.

Usa for cuando conoces de antemano el conjunto de elementos a iterar: una lista, un rango, un diccionario. Usa while cuando no sabes cuántas iteraciones necesitarás y la repetición depende de que se cumpla una condición dinámica. En Python el for cubre la gran mayoría de los casos gracias a range() y a que casi todo es iterable por defecto.
El resultado de iterar sobre ambos es idéntico, pero range(5) no crea la lista en memoria — genera los números uno a uno cuando el bucle los necesita. range(1_000_000) ocupa unos 48 bytes constantes en memoria, mientras que list(range(1_000_000)) ocuparía cerca de 8 MB. Para bucles normales la diferencia es irrelevante, pero para rangos muy grandes range() es claramente superior.
Con enumerate(). Es la forma idiomática de Python: for i, valor in enumerate(lista). Evita el patrón for i in range(len(lista)): valor = lista[i] que es más verboso y propenso a errores de índice. enumerate() admite un parámetro start para empezar desde cualquier número: enumerate(lista, start=1).
El bloque else de un for se ejecuta cuando el bucle termina de forma natural, es decir, sin que se ejecutara un break. Es especialmente útil en patrones de búsqueda: si encuentras el elemento haces break y el else no corre; si terminas el bucle sin encontrarlo el else se ejecuta y puedes manejar el caso "no encontrado" limpiamente, sin necesitar una variable bandera booleana.
No directamente — modificar una lista mientras la recorres con for produce comportamiento indefinido: puedes saltarte elementos o procesar el mismo dos veces. La solución es iterar sobre una copia: for item in lista[:] o for item in list(lista). La forma más limpia es usar una list comprehension: nueva = [item for item in lista if condicion], que crea una lista nueva sin modificar la original durante la iteración.
Sí. zip() acepta cualquier número de iterables: zip(a, b, c, d). Produce tuplas de N elementos, una por posición. Se detiene cuando el iterable más corto se agota. Para continuar hasta el más largo y rellenar con un valor por defecto, usa itertools.zip_longest(a, b, fillvalue=None). Para hacer zip "inverso" (desempaquetar tuplas), usa el operador asterisco: nombres, notas = zip(*lista_de_pares).
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Bucle for en Python: guía completa con range, enumerate y zip? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!