Bucles e iteradores en Ruby: each, times, while y for
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
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:
- Control Expressions:
while,until,for(no crea ámbito y «se usa poco en los programas Ruby modernos»), modificadores,begin … end while,break,next,redoy el error deretryfuera derescue. - Integer:
times,uptoydownto. - Numeric#step: formas con argumentos posicionales y con
by:yto:. - Kernel#loop: repite el bloque y termina con
breakoStopIteration. - Proc y NEWS de Ruby 3.4: el parámetro implícito
it, añadido en Ruby 3.4. - Array, Enumerable, Hash y String:
each,each_with_index,map,find,each_key,each_valueyeach_char. - RuboCop 1.92, regla Lint/Loop: señala los
begin … end while/until.
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.