Polyglot
Languages Ruby loops
Ruby § loops

Loops

Ruby admits several iteration forms, but the conventional discipline favours iterator methods (each, map, select, reduce, etc.) over explicit loops. The explicit forms are while (loop while condition is true), until (loop while condition is false), loop (infinite — exit with break), and for/in (rarely used). The conventional Ruby idiom is (1..10).each { |n| ... } rather than for n in 1..10. The combination — iterator methods as the conventional default, explicit forms for unusual cases, blocks as the principal iteration mechanism, the substantial Enumerable methods — is the substance of Ruby’s iteration surface.

while and until

while condition
  body
end

until condition                                   # equivalent to while !condition
  body
end

Examples:

i = 0
while i < 10
  puts i
  i += 1
end

n = 100
until n <= 0
  n -= 5
end

The modifier (postfix) forms:

i += 1 while i < 10
n -= 5 until n <= 0

The conventional discipline:

  • Use while/until when the condition is the natural termination signal.
  • Use the modifier form for short single-statement loops.
  • Prefer iterator methods for substantial iteration.

loop

The loop method admits an infinite loop:

loop do
  input = gets
  break if input.chomp == "quit"
  puts "got: #{input}"
end

The conventional uses are read-eval-print loops, retry loops, and event loops — any loop where termination is internal (via break).

loop is a method — not a keyword. It accepts a block. The mechanism admits substantial flexibility:

result = loop do
  attempt = try_once
  break attempt if attempt.success?
end

The break value admits returning a value from the loop.

for/in

The for/in admits the C-family iteration form:

for x in [1, 2, 3]
  puts x
end

for i in 1..10
  puts i
end

The form is rare in idiomatic Ruby. The conventional alternative is each:

[1, 2, 3].each { |x| puts x }
(1..10).each { |i| puts i }

The principal differences:

  • for does not introduce a new scope; the loop variable persists after the loop.
  • each introduces a block scope; the variable is local.
for x in [1, 2, 3]
  # ...
end
puts x                                            # 3 (still in scope)

[1, 2, 3].each { |y| ... }
# puts y                                          # NameError

The block-scoping is the conventional reason to prefer each.

Iterator methods

The conventional Ruby iteration uses methods on collections:

[1, 2, 3].each { |n| puts n }                     # iterate
[1, 2, 3].map { |n| n * 2 }                       # transform → [2, 4, 6]
[1, 2, 3].select { |n| n.odd? }                   # filter → [1, 3]
[1, 2, 3].reject { |n| n.odd? }                   # filter out → [2]
[1, 2, 3].reduce(0) { |sum, n| sum + n }          # aggregate → 6
[1, 2, 3].count { |n| n > 1 }                     # count → 2
[1, 2, 3].any? { |n| n > 2 }                      # → true
[1, 2, 3].all? { |n| n > 0 }                      # → true
[1, 2, 3].find { |n| n > 1 }                      # first match → 2

Treated in Enumerable.

times, upto, downto, step

Integer methods admit C-style index loops:

5.times do |i|
  puts i                                          # 0, 1, 2, 3, 4
end

1.upto(5) { |i| puts i }                          # 1, 2, 3, 4, 5
5.downto(1) { |i| puts i }                        # 5, 4, 3, 2, 1
0.step(20, 5) { |i| puts i }                      # 0, 5, 10, 15, 20

(1..10).step(2) { |i| puts i }                    # 1, 3, 5, 7, 9

The forms are conventional for index-driven iteration; for collections, each_with_index is the conventional alternative.

each_with_index

["a", "b", "c"].each_with_index do |item, i|
  puts "#{i}: #{item}"
end
# 0: a
# 1: b
# 2: c

# Or with index first:
["a", "b", "c"].each.with_index(1) do |item, i|   # custom start
  puts "#{i}. #{item}"
end
# 1. a
# 2. b
# 3. c

break, next, redo

[1, 2, 3, 4, 5].each do |n|
  break if n > 3                                  # exit the loop
  next if n.even?                                 # skip to next iteration
  puts n                                          # 1, 3
end

The break exits the innermost loop; next proceeds to the next iteration. The redo re-executes the current iteration without changing the index — rarely used.

break value returns the given value from the loop:

result = [1, 2, 3].each do |n|
  break "found #{n}" if n == 2
end
puts result                                       # "found 2"

break works in iterator methods too:

([1, 2, 3].map { |n| break "stopped" if n > 2; n * 2 })
# "stopped"

return from a block

return inside a block returns from the enclosing method — not from the block:

def find_positive(arr)
  arr.each do |n|
    return n if n > 0                             # returns from find_positive
  end
  nil
end

find_positive([-1, -2, 3, -4])                    # 3

The mechanism admits substantial control flow; for “return from block only”, next returns from the block:

results = arr.map do |x|
  next 0 if x.nil?                                # returns 0 from this block iteration
  x * 2
end

Range iteration

(1..10).each { |n| puts n }                       # 1 to 10
(1...10).each { |n| puts n }                      # 1 to 9
(1..).take(5).each { |n| puts n }                 # endless range; take 5

# Step:
(1..20).step(3) { |n| puts n }                    # 1, 4, 7, 10, 13, 16, 19

# Reverse:
10.downto(1) { |n| puts n }                       # 10, 9, ..., 1
(1..10).to_a.reverse.each { |n| puts n }
(1..10).reverse_each { |n| puts n }

each_slice and each_cons

[1, 2, 3, 4, 5, 6].each_slice(2) { |s| puts s.inspect }
# [1, 2]
# [3, 4]
# [5, 6]

[1, 2, 3, 4, 5].each_cons(3) { |s| puts s.inspect }
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]

The forms admit substantial windowing patterns.

Common patterns

Accumulating a sum

# Imperative:
sum = 0
[1, 2, 3, 4, 5].each { |n| sum += n }

# Functional:
sum = [1, 2, 3, 4, 5].sum                         # 15

# Or with reduce:
sum = [1, 2, 3, 4, 5].reduce(0, :+)
sum = [1, 2, 3, 4, 5].reduce(0) { |a, b| a + b }

Transforming a collection

# Imperative:
result = []
items.each { |x| result << x.upcase }

# Functional:
result = items.map(&:upcase)

Filtering

# Imperative:
result = []
items.each { |x| result << x if x.length > 5 }

# Functional:
result = items.select { |x| x.length > 5 }

Iterating with index

items.each_with_index do |item, i|
  puts "[#{i}] #{item}"
end

# Or with map:
items.map.with_index { |item, i| "[#{i}] #{item}" }

Range over numbers

total = (1..100).sum                              # 5050
squares = (1..10).map { |n| n * n }
even_count = (1..100).count(&:even?)              # 50

Loop with explicit termination

loop do
  job = queue.dequeue
  break if job.nil?
  process(job)
end

Polling

until ready?
  sleep 0.1
end

Counter pattern

counts = Hash.new(0)
words.each { |w| counts[w] += 1 }

The Hash.new(0) admits substantial conciseness — missing keys default to 0.

Searching with find

result = items.find { |x| x.matches? }
if result
  process(result)
end

# Or return the result of an iterator method:
items.find { |x| x.id == target }

Eager vs lazy iteration

# Eager (constructs the entire intermediate array):
(1..Float::INFINITY).map { |n| n * n }.first(5)   # never returns

# Lazy:
(1..Float::INFINITY).lazy.map { |n| n * n }.first(5)
                                                   # [1, 4, 9, 16, 25]

The lazy admits substantial efficiency for substantial or infinite collections.

zip for parallel iteration

names = ["alice", "bob", "charlie"]
ages = [30, 25, 35]

names.zip(ages).each do |name, age|
  puts "#{name}: #{age}"
end

Chained iteration

result = items
  .reject(&:archived?)
  .map(&:title)
  .sort
  .first(10)

The pattern admits substantial functional-style processing.

Iterating with state

total = 0
running_totals = items.map do |x|
  total += x
  total
end
# Or:
running_totals = items.each_with_object([0]) do |x, totals|
  totals << totals.last + x
end[1..]

The each_with_object admits “iterate while mutating an accumulator”.

inject / reduce for aggregation

# Sum:
sum = [1, 2, 3].inject(0) { |a, b| a + b }
sum = [1, 2, 3].inject(:+)                        # symbol shorthand
sum = [1, 2, 3].sum                               # built-in

# Maximum:
max = [3, 1, 4, 1, 5, 9].inject { |a, b| a > b ? a : b }
max = [3, 1, 4, 1, 5, 9].max

# Build a hash:
counts = words.inject(Hash.new(0)) { |h, w| h[w] += 1; h }

# Or with each_with_object:
counts = words.each_with_object(Hash.new(0)) { |w, h| h[w] += 1 }

Generators with Enumerator

fibonacci = Enumerator.new do |y|
  a, b = 0, 1
  loop do
    y << a
    a, b = b, a + b
  end
end

fibonacci.first(10)                               # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

The form admits substantial lazy generators.

loop with retry

attempts = 0
loop do
  attempts += 1
  result = try_operation
  break result if result.success?
  break nil if attempts >= 3
  sleep 1
end

A note on iteration vs explicit loops

The conventional Ruby discipline strongly favours iteration methods over explicit loops:

# Conventional (iteration method):
items.each { |x| process(x) }
items.map { |x| transform(x) }
items.select { |x| keep?(x) }

# Less conventional (explicit loop):
i = 0
while i < items.length
  process(items[i])
  i += 1
end

# Even less conventional:
for item in items
  process(item)
end

The iteration-method form admits:

  • Block-scoped iteration variables — no leak.
  • Substantial composition — chain select.map.first(5).
  • Enumerable mixin — many methods derived from each.
  • Lazy evaluation via lazy.
  • Better integration with the standard library.

A note on the conventional discipline

The contemporary Ruby loops advice:

  • Use each for plain iteration.
  • Use map, select, reject, reduce for transformations.
  • Use times, upto, downto, step for index-driven loops.
  • Use ranges for numeric iteration.
  • Use loop for explicit infinite loops with internal termination.
  • Use while/until for condition-driven loops.
  • Use modifier forms (postfix while/until) for short loops.
  • Use each_with_index for index access.
  • Use each_with_object for accumulator patterns.
  • Use lazy for substantial or infinite sequences.
  • Avoid for/in — use each instead.
  • Use break value to return from a loop.
  • Use return to return from the enclosing method (not just the block).

The combination — while/until/loop for explicit loops, times/upto/step/each for iteration, the substantial Enumerable methods derived from each, the rare-but-admitted for/in form — is the substance of Ruby’s iteration surface. The discipline produces concise, expressive iteration code that admits substantial composition.