Skip to content

Loops

Frankie has four loop constructs and a rich set of iterator methods. The loop constructs handle index-based and condition-based repetition; the iterators handle collection traversal.


while

Runs as long as the condition is truthy.

x = 0
while x < 5
  puts x
  x += 1
end
# 0 1 2 3 4

until

Runs as long as the condition is falsy — the inverse of while.

x = 0
until x == 5
  puts x
  x += 1
end
# 0 1 2 3 4

do...while

Runs the body at least once, then checks the condition. Borrowed from Fortran.

x = 10
do
  puts x
  x += 1
while x < 5
# prints 10 — body ran once even though condition was already false

Useful when you need to guarantee at least one execution — user input loops, retry logic:

do
  input = input("Enter a positive number: ")
  n = input.to_i
while n <= 0

puts "You entered: #{n}"

for...in

Iterates over a range or a vector.

# Over a range
for i in 1..5
  puts i
end
# 1 2 3 4 5

# Exclusive range
for i in 0...4
  puts i
end
# 0 1 2 3

# Over a vector
for item in ["apple", "banana", "cherry"]
  puts item
end

# Over a hash — gives [key, value] pairs
config = {host: "localhost", port: 5432}
for pair in config
  puts "#{pair[0]}: #{pair[1]}"
end

n.times

Repeat a block exactly n times. The loop variable counts from 0 to n-1.

3.times do |i|
  puts "iteration #{i}"
end
# iteration 0
# iteration 1
# iteration 2

# Without the variable
5.times do
  print "* "
end
# * * * * *

The standalone times(n) form is identical:

times(3) do |i|
  puts i
end

Loop Control

next

Skip to next iteration

Equivalent to continue in other languages.

[1, 2, 3, 4, 5, 6].each do |n|
  next if n % 2 == 0
  puts n
end
# 1 3 5

break

Exit the loop early

[1, 2, 3, 4, 5].each do |n|
  break if n > 3
  puts n
end
# 1 2 3

break with a value

A break can carry a value out of the loop:

result = [1, 2, 3, 4, 5].each do |n|
  break n * 10 if n == 3
end
puts result   # 30

Postfix forms

next if condition
break if condition

Iterator Methods

For collection traversal, iterator methods with blocks are more idiomatic than for loops. They compose, chain, and express intent more clearly.

.each — iterate for side effects

[1, 2, 3].each do |x|
  puts x * 2
end

{name: "Alice", age: 30}.each do |k, v|
  puts "#{k}: #{v}"
end

.each_with_index — iterate with position

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

.each_with_object — iterate with accumulator

result = [1, 2, 3, 4, 5].each_with_object({}) do |x, h|
  h[x] = x * x
end
puts result   # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

.each_slice — iterate in fixed-size chunks

[1, 2, 3, 4, 5, 6].each_slice(2) do |slice|
  puts slice
end
# [1, 2]
# [3, 4]
# [5, 6]

.each_cons — iterate with sliding window

[1, 2, 3, 4, 5].each_cons(3) do |window|
  puts window
end
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]

.times — iterate n times (method form)

5.times do |i|
  puts i
end

for vs Iterators

Both work. Iterators are generally preferred for collection traversal because they compose and chain cleanly:

# for loop
for n in [1, 2, 3, 4, 5]
  puts n if n > 3
end

# Iterator — composes with select
[1, 2, 3, 4, 5].select do |n|
  n > 3
end.each do |n|
  puts n
end

Use for when iterating over a range by index. Use iterators when transforming or filtering a collection.


Infinite Loops

A while true loop runs forever until break:

count = 0
while true
  count += 1
  break if count >= 5
end
puts count   # 5

Nested Loops

[1, 2, 3].each do |x|
  [10, 20].each do |y|
    puts "#{x} × #{y} = #{x * y}"
  end
end
# 1 × 10 = 10
# 1 × 20 = 20
# 2 × 10 = 20
# ...

# Or use .product for cartesian iteration
[1, 2, 3].product([10, 20]).each do |pair|
  puts "#{pair[0]} × #{pair[1]} = #{pair[0] * pair[1]}"
end