Repetition, ranges, and saying no to a loop
while and until, the times shortcut, each over a range, and the two words that steer a loop from inside: next and break.
While, until, and counting
while repeats as long as a condition is true, and until repeats until it becomes true — the same loop with the question flipped, for the days when one of them reads better. Both need someone inside to change the condition, or the loop runs forever; changing it is normally the last line of the body.
For a count you already know, 3.times is the Ruby way, and the block receives the number if you give it a name. A loop variable declared with times or each belongs to the loop — it is gone when the loop ends. That containment is one of the small design choices that makes long programs calmer to read.
Ranges, and each
A range is written with dots: 1..5 includes both ends, and 1...5 excludes the last one. Ranges are values — you can print them, test them with include?, and walk them with each, which is the version of the loop most Ruby code actually uses: (1..5).each { |n| puts n }.
each is not the only iterator, and later chapters lean on its relatives heavily: each_with_index when the position matters, upto and downto for a plain count in a direction, and step for every other number. All of them take a block — the piece of code between do ... end or braces — and blocks are the subject of chapter eight.
Next and break
next skips the rest of this pass and starts the next one; break leaves the loop entirely. Together they express "except this one" and "that is enough" without turning the loop condition inside out, and they are the reason a loop can stay readable when the logic gets fussy.
A loop whose result you care about is usually better written as a collection method later, but a loop you break out of is still the clearest way to say "look through these until you find it" — especially when the search itself is expensive.
What a loop actually repeats
A while checks its condition before every pass, including the first, so a condition that starts false means the body never runs. until is the same loop with the test inverted. The condition has to change inside the body; when it does not, the loop is infinite, and the only way out is the one the runner eventually takes.
each is different in shape but not in purpose: it walks the collection and hands each item to your block. The block variable lives only for that pass, and each returns the collection itself when it finishes, not a result. When you need the answer from every pass, the collection methods in the next chapter are the ones that collect it.
The counter belongs to the loop
A while loop needs three parts: a name with a starting value, a condition that reads it, and a line in the body that moves it. Leave out the third and the condition never changes; leave out the first and the name is undefined. Writing the three parts in that order before the body is a reliable way to avoid both.
With each and times, Ruby owns the counter. You cannot forget to increment it, and you cannot accidentally reuse it afterwards, because the block parameter is local to the pass. That is the main reason the iterator forms are preferred for anything with a known collection.
A range is a value
1..5 is not syntax inside a loop; it is an object you can store, pass to a method, and test with cover? or include?. That is why case can use a range in a when, and why a method can accept a range instead of two numbers and read better at the call site.
The classic loop bugs are small and quiet. Forgetting the line that changes the counter gives an infinite loop. break leaves only the innermost loop, so a break inside a nested pair still lets the outer one continue. And the dot count matters: 1..5 runs five times while 1...5 runs four, an off-by-one that is easy to miss when the numbers are not printed.
Every loop shape
count = 3
while count > 0
puts "count: #{count}"
count = count - 1
end
attempt = 0
until attempt >= 2
puts "attempt #{attempt}"
attempt = attempt + 1
end
3.times do |n|
puts "square of #{n} is #{n * n}"
end
(1..5).each { |n| puts n if n.odd? }
1.upto(3) { |n| puts "up #{n}" }
3.downto(1) { |n| puts "down #{n}" }
(0..10).step(5) { |n| puts "step #{n}" }
# next skips, break stops.
(1..10).each do |n|
next if n.even?
puts "odd: #{n}"
break if n >= 7
endA savings plan
Run it and watch the balance grow. Then change the first loop into an until, and write a loop that counts down from ten and prints only the even numbers.
Prefer the form that makes the loop's job obvious: 3.times says "do this three times", until done says "keep going until it is", and neither makes the reader hunt for the update line that makes it end.
Try these next
- Count down from ten to one with
downto, then print a single line at the end. - Sum the odd numbers from one to twenty with a loop that uses
next. - Find the first number above fifty that is divisible by seven, and
breakas soon as you have it.
A five-day ledger
Run it and follow the running total. Then change the number of days, and add a second loop that prints only the days where the total crossed a hundred.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
Which numbers does 1...5 include?
