Nivel inicial12 min de lectura · Publicado el

Bucles e iteradores en Ruby: each, times, while y for

Bucles e iteradores en Ruby: while reparte hojas mientras quede alguna en el montón; each da una a cada persona de la lista. while vigila; each recorrerubentejera.com whilemientras quede algunatú vigilas el montóneachuna a cada personala lista sabe cuántas
El montón es el while: repartes mientras quede alguna hoja y tú vigilas la condición. La lista de asistentes es el each: da una a cada persona y ya sabe cuántas hay, sin que lleves la cuenta.

En Ruby hay dos formas de repetir código. Los bucles (while, until, for, loop) repiten mientras se cumpla una condición. Los iteradores (each, times, map…) son métodos que recorren algo por ti y te dan cada elemento.

Piensa en repartir las hojas en una reunión. Un bucle es decir «sigue repartiendo mientras quede alguna en el montón»: tú vigilas la condición. Un iterador es decir «dale una a cada persona de la lista»: no cuentas nada, la lista ya sabe cuántas personas hay.

En Ruby lo normal es usar iteradores. Son más cortos y no tienes que llevar la cuenta a mano, que es donde suelen colarse los errores.

Todos los ejemplos están ejecutados en Ruby 4.0.9 y la salida que ves es la real.

Chuleta: quiero… → uso…

Quiero… Uso… Ejemplo
Recorrer cada elemento de un array, rango o hash each frutas.each { |fruta| puts fruta }
Recorrer y saber la posición each_with_index o each.with_index(1) frutas.each_with_index { |fruta, i| … }
Obtener un array nuevo a partir de otro map precios.map { |p| p * 2 }
Repetir algo N veces times 3.times { … }
Contar de un número a otro upto / downto 1.upto(10) { … }
Contar de dos en dos (o de N en N) step 1.step(10, 2) { … }
Recorrer las letras de un texto each_char "MARTA".each_char { … }
Repetir mientras algo sea verdad while while vidas > 0
Repetir hasta que algo sea verdad until until vidas == 0
Repetir sin fin hasta que decida salir loop + break loop do … break if … end
Ejecutar al menos una vez (el «do while») loop + break al final Ver más abajo
Salir del bucle ya break break if numero > 4
Saltar a la siguiente vuelta next next if numero.even?
Buscar el primero que cumple algo find [3, 8, 12].find { it > 5 }

each: recorrer una colección

each le pasa al bloque cada elemento, uno detrás de otro. Un bloque es el trozo de código entre do … end (o entre llaves), y lo que va entre barras, |fruta|, es el nombre que le das al elemento de cada vuelta.

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

frutas.each do |fruta|
  puts "Me como una #{fruta}"
end

# Me como una manzana
# Me como una pera
# Me como una uva

Si el bloque cabe en una línea, se suelen usar llaves:

frutas.each { |fruta| print fruta, " " }

# manzana pera uva

Desde Ruby 3.4, si el bloque recibe un solo valor, puedes llamarlo it sin declararlo:

frutas.each { puts it.upcase }

# MANZANA
# PERA
# UVA

Si quieres saber más sobre bloques y do … end, lo explico en Bloques en Ruby.

Rangos

Un rango es una secuencia de un valor a otro. Con dos puntos incluye el último; con tres, no:

("a".."f").each { |letra| print letra, " " }
# a b c d e f

(1..5).each { |numero| print numero, " " }
# 1 2 3 4 5

(1...5).each { |numero| print numero, " " }
# 1 2 3 4

Strings: each_char

Un texto no tiene each, pero sí each_char, que te da cada carácter:

"MARTA".each_char { |caracter| puts "Carácter --> #{caracter}" }

# Carácter --> M
# Carácter --> A
# Carácter --> R
# Carácter --> T
# Carácter --> A

Hashes: each, each_key y each_value

En un hash, each te da la clave y el valor a la vez. Si solo necesitas una de las dos cosas, usa each_key o each_value:

contactos = {
  alberto: "600000000",
  barreto: "611111111",
  carmelo: "622222222"
}

contactos.each do |nombre, telefono|
  puts "#{nombre}: #{telefono}"
end
# alberto: 600000000
# barreto: 611111111
# carmelo: 622222222

contactos.each_key { |nombre| puts "Nombre: #{nombre}" }
# Nombre: alberto
# Nombre: barreto
# Nombre: carmelo

contactos.each_value { |telefono| puts "Teléfono: #{telefono}" }
# Teléfono: 600000000
# Teléfono: 611111111
# Teléfono: 622222222

Más sobre hashes en Hashes en Ruby.

each_with_index: recorrer con la posición

Cuando además del elemento necesitas su posición, usa each_with_index. La posición empieza en 0:

frutas.each_with_index do |fruta, indice|
  puts "#{indice}: #{fruta}"
end

# 0: manzana
# 1: pera
# 2: uva

Si quieres empezar a contar en 1 (para mostrar un ranking, por ejemplo), usa each.with_index(1). Así no tienes que sumar 1 a mano:

frutas.each.with_index(1) do |fruta, posicion|
  puts "#{posicion}º #{fruta}"
end

# 1º manzana
# 2º pera
# 3º uva

map: transformar una lista en otra

each sirve para hacer algo con cada elemento. Si lo que quieres es obtener una lista nueva, usa map: devuelve un array con lo que devuelve el bloque en cada vuelta.

precios = [10, 20, 30]

con_iva = precios.map { |precio| precio * 1.21 }
p con_iva
# [12.1, 24.2, 36.3]

p precios.map { it * 2 }
# [20, 40, 60]

El array original no cambia. Si te ves creando un array vacío y llenándolo con << dentro de un each, casi siempre puedes sustituirlo por un map.

times: repetir N veces

times repite el bloque tantas veces como indique el número. El valor que recibe el bloque empieza en 0:

3.times do |vuelta|
  puts "Vuelta #{vuelta}"
end

# Vuelta 0
# Vuelta 1
# Vuelta 2

Si quieres mostrar la cuenta desde 1, suma 1 al escribirla. No hace falta una variable contadora aparte:

3.times { |vuelta| puts "Iteración #{vuelta + 1}" }

# Iteración 1
# Iteración 2
# Iteración 3

upto, downto y step: contar de un número a otro

upto sube hasta un número y downto baja hasta él. Los dos incluyen el último:

1.upto(10) { |numero| print numero, " " }
# 1 2 3 4 5 6 7 8 9 10

10.downto(1) { |numero| print numero, " " }
# 10 9 8 7 6 5 4 3 2 1

step cuenta de N en N. Estas tres formas hacen lo mismo; la segunda, con nombres, se entiende mejor si no recuerdas el orden de los argumentos:

1.step(10, 2) { |numero| print numero, " " }
# 1 3 5 7 9

1.step(by: 2, to: 10) { |numero| print numero, " " }
# 1 3 5 7 9

(1..10).step(2) { |numero| print numero, " " }
# 1 3 5 7 9

while y until en Ruby: repetir mientras (o hasta que)

while repite mientras la condición sea verdadera. Úsalo cuando no sabes de antemano cuántas vueltas habrá:

vidas = 3
while vidas > 0
  puts "Sigo vivo. Vidas: #{vidas}"
  vidas -= 1
end

# Sigo vivo. Vidas: 3
# Sigo vivo. Vidas: 2
# Sigo vivo. Vidas: 1

until es lo contrario: repite hasta que la condición sea verdadera. Este código hace lo mismo que el anterior:

vidas = 3
until vidas == 0
  puts "Sigo vivo. Vidas: #{vidas}"
  vidas -= 1
end

Elige el que se lea mejor en voz alta. «Hasta que no queden vidas» suele ser más claro que «mientras no sea cierto que no quedan vidas».

En una sola línea (modificador)

Si el cuerpo es una sola expresión, puedes poner el while o el until detrás. Es lo que la documentación de Ruby llama modificador:

medicion = 0
medicion += 1 while medicion < 1000
p medicion
# 1000

medicion -= 1 until medicion.zero?
p medicion
# 0

Con if pasa lo mismo, aunque no es un bucle. Estas dos formas son equivalentes:

medicion = 30

if medicion > 0
  puts "Entro"
end

puts "Entro" if medicion > 0

Más sobre if, unless y compañía en Condicionales en Ruby.

loop: repetir hasta que decidas salir

loop repite el bloque para siempre. Sales con break cuando se cumple lo que tú decidas. Encaja cuando la condición de salida está en mitad del bloque, como al preguntar al usuario:

loop do
  print "¿Otra vuelta? (s/n) "
  respuesta = gets&.chomp
  break if respuesta.nil? || respuesta == "n"

  puts "Vamos a por otra vuelta"
end
puts "Fin"

Si respondes s y después n, ves esto:

¿Otra vuelta? (s/n) s
Vamos a por otra vuelta
¿Otra vuelta? (s/n) n
Fin

gets lee una línea de lo que escribes y chomp le quita el salto de línea final. gets devuelve nil si ya no hay nada que leer (por ejemplo, si pulsas Ctrl + D); el &. evita que el programa falle en ese caso, y la comprobación de nil hace que salga del bucle en lugar de repetirlo para siempre.

do while en Ruby

Ruby no tiene do … while como otros lenguajes, y while comprueba la condición antes de entrar. Si necesitas que el cuerpo se ejecute al menos una vez antes de comprobar la condición, la forma más clara es loop con el break al final:

intentos = 0
loop do
  intentos += 1
  puts "Intento #{intentos}"
  break unless intentos < 3
end

# Intento 1
# Intento 2
# Intento 3
Dónde se comprueba la condición. Con while, primero la condición: si se cumple, se ejecuta el cuerpo y se vuelve a comprobar; si no, fin. Con loop y el break al final, primero el cuerpo y después la condición: si se cumple, otra vuelta; si no, fin. Así el cuerpo se ejecuta al menos una vez.
La diferencia está en dónde va la condición, en ámbar. while la comprueba antes de entrar; con loop y el break al final, el cuerpo va primero y se ejecuta al menos una vez.

También existe begin … end while, que la documentación oficial recoge y que ejecuta el cuerpo una vez aunque la condición sea falsa desde el principio:

contador = 10
begin
  puts "Se ejecuta una vez aunque la condición sea falsa"
end while contador < 3

# Se ejecuta una vez aunque la condición sea falsa

Funciona, pero es fácil confundirlo con el modificador de una línea, que sí comprueba la condición antes. RuboCop, el analizador de estilo más usado en Ruby, lo marca con la regla Lint/Loop. Por eso es preferible loop con break.

for en Ruby (for…in): funciona, pero casi no se usa

for numero in [1, 2, 3]
  print numero
end
# 123

for se parece a each, y de hecho los valores los obtiene de each. Pero tiene una diferencia importante: no crea un ámbito propio para sus variables. La variable del for sigue existiendo cuando el bucle termina; la de un bloque, no:

for numero in [1, 2, 3]
end
p numero
# 3

[4, 5, 6].each { |otro| }
p defined?(otro)
# nil

Es como dejar los platos sucios en la mesa al acabar de comer: no pasa nada hasta que alguien se sienta y se encuentra lo que dejaste. Con each, la variable desaparece al terminar y no puede pisar a otra con el mismo nombre.

La documentación de Ruby lo dice claro: el bucle for se usa poco en los programas Ruby modernos. Si vienes de otro lenguaje y buscas el «for de Ruby», casi siempre lo que quieres es each, times o upto.

break y next: controlar el bucle

next salta el resto de la vuelta actual y pasa a la siguiente. break sale del bucle del todo:

[1, 2, 3, 4, 5, 6].each do |numero|
  next if numero.even?   # los pares, fuera
  break if numero > 4    # al llegar al 5, se acabó
  puts numero
end

# 1
# 3

Los dos aceptan un valor. Con break ese valor es el resultado del bucle; con next, el resultado de esa vuelta del bloque:

primero_grande = [3, 8, 12].each { |n| break n if n > 5 }
p primero_grande
# 8

p (1..6).map { |n| next 0 if n.odd?; n }
# [0, 2, 0, 4, 0, 6]

Si lo que buscas es el primer elemento que cumple algo, find lo dice más claro que un each con break:

p [3, 8, 12].find { it > 5 }
# 8

redo y retry

redo repite la vuelta actual, con el mismo elemento y sin volver a comprobar la condición. Se usa muy poco, porque es fácil que acabe en un bucle infinito:

intentos = 0
[1, 2].each do |n|
  intentos += 1
  redo if n == 2 && intentos < 4
  puts "n=#{n}, intentos=#{intentos}"
end

# n=1, intentos=1
# n=2, intentos=4

retry ya no sirve para reiniciar un bucle. Eso funcionaba en Ruby 1.8; desde entonces solo puede usarse dentro de un rescue, para volver a intentar un bloque begin que ha fallado. Fuera de un rescue, Ruby da un error de sintaxis:

retry.rb:1: syntax error found (SyntaxError)
> 1 | retry_test = [1].each { retry }
    |                         ^~~~~ Invalid retry without rescue

Así se usa hoy:

reintentos = 0
begin
  reintentos += 1
  raise "fallo" if reintentos < 3
  puts "Funcionó al intento #{reintentos}"
rescue
  retry
end

# Funcionó al intento 3

Ponle siempre un límite: si el fallo no se arregla solo, retry sin límite repite para siempre.

Crea tu propio iterador con yield

each o times no tienen nada de mágico: son métodos que llaman a tu bloque con yield. Cada yield ejecuta el bloque una vez:

def three_times
  yield
  yield
  yield
end

three_times { puts "HOLA" }

# HOLA
# HOLA
# HOLA

yield también puede pasarle valores al bloque. Este método genera la serie de Fibonacci hasta un máximo y le da cada número al bloque:

def fib_up_to(max)
  i1, i2 = 1, 1 # asignación paralela: i1 = 1 e i2 = 1
  while i1 <= max
    yield i1
    i1, i2 = i2, i1 + i2
  end
end

fib_up_to(1000) { |f| print f, " " }

# 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Fíjate en que el while queda escondido dentro del método. Quien lo usa solo ve un iterador: dice qué hacer con cada número y no se preocupa de cómo se calculan (cómo pasa el control con yield, en Bloques en Ruby).

Siguiente paso

Coge un bucle while o for de tu código y prueba a reescribirlo con la forma de la chuleta que le corresponda. Si al cambiarlo desaparece una variable contadora, vas por buen camino.

Antes de tocarlo, ten un test que compruebe lo que hace el bucle: si sigue en verde después del cambio, sabes que el comportamiento es el mismo. Es el paso de refactorizar del algoritmo de TDD.

Fuentes

Documentación oficial de Ruby 4.0, consultada en noviembre de 2026:

Todos los ejemplos se han ejecutado con Ruby 4.0.9 en la imagen oficial de Docker (docker run --rm ruby:latest ruby ejemplos.rb), y la salida mostrada es la que devolvió. El ejemplo de each_char, tras cambiar el texto de ejemplo, se ha vuelto a ejecutar el 8 de noviembre de 2026 con la misma versión.