while and until Loops

Published by Programming Line in Ruby. Last updated .

A while loop repeats a block of code for as long as a condition stays true, and Ruby’s until loop is its mirror image: it repeats for as long as a condition stays false. Together they are the workhorses of Ruby’s control flow whenever you don’t know in advance how many times you need to iterate — reading input until it runs out, retrying an operation until it succeeds, or draining a queue until it’s empty. This lesson covers how these loops work internally, every syntax form Ruby offers (the block form, the one-line modifier form, and the begin...end while variant), the mistakes that trip up almost everyone at least once, and the idioms experienced Rubyists reach for instead.

Overview: How while and until Work

Both while and until are pre-test loops: Ruby checks the condition before running the loop body. If the condition is already false for while (or already true for until), the body never runs — not even once. That is different from the special begin...end while form covered later in this lesson, which is the one construct in Ruby that guarantees the body runs at least once.

while and until are language keywords handled directly by Ruby’s parser, not method calls — unlike almost everything else in Ruby, where even 1 + 1 is really a call to Integer#+ and if is itself an expression that returns a value. Even so, a while or until loop is still an expression: it evaluates to nil when it finishes normally, or to whatever value you pass to break if you exit early with one. This trips people up when they assume the loop “returns” the last value computed inside it — it does not, unless you say so explicitly with break value.

Ruby’s truthiness rules apply here exactly as they do everywhere else in the language: only nil and false are falsy. The integer 0, an empty string "", and an empty array [] are all truthy in Ruby, unlike in languages such as C, JavaScript, or Python. That means while count loops forever if count is 0, and while list loops forever if list is [] — you must write an explicit comparison, such as while count > 0 or until list.empty?.

until condition means exactly the same thing as while !condition. Ruby gives you until purely for readability, so you can express the positive idea you actually mean (“until the queue is empty”) instead of a double negative (“while the queue is not empty”).

Form Runs body while… Stops when…
while cond cond is truthy cond becomes falsy (nil or false)
until cond cond is falsy cond becomes truthy
begin...end while cond always runs once, then while cond is truthy cond becomes falsy (checked after each run)

Syntax

The general forms look like this:

while condition
  # code to execute repeatedly
end

until condition
  # code to execute repeatedly
end

# modifier (one-line) form
statement while condition
statement until condition
  • condition — any Ruby expression; it is re-evaluated fresh before every single iteration, not just once at the start.
  • The loop body is indented by convention (two spaces) and terminated with a matching end.
  • The modifier form lets you write a single statement followed by while or until and the condition, with no end needed — handy for short loops.

Here is the modifier form in a complete, runnable example:

i = 1
i += 1 while i <= 3
puts i

Output:

4

i starts at 1. Ruby checks i <= 3 before each increment, so the statement i += 1 runs while i is 1, 2, and 3, leaving i at 4 once 4 <= 3 finally fails.

Examples

Example 1: Counting up with while

count = 1
while count <= 5
  puts "Count is #{count}"
  count += 1
end
puts "Done!"

Output:

Count is 1
Count is 2
Count is 3
Count is 4
Count is 5
Done!

count starts at 1. Before each pass, Ruby checks count <= 5; as long as that’s true it prints the current count and increments it. Once count becomes 6, the condition is false, the loop exits, and execution continues with puts "Done!".

Example 2: Counting down with until

countdown = 5
until countdown.zero?
  puts "T-minus #{countdown}"
  countdown -= 1
end
puts "Liftoff!"

Output:

T-minus 5
T-minus 4
T-minus 3
T-minus 2
T-minus 1
Liftoff!

Here until countdown.zero? reads naturally as “keep going until countdown reaches zero.” It’s equivalent to while countdown != 0, but the positive phrasing is easier to follow at a glance — this is exactly the kind of place until earns its keep.

Example 3: Draining a queue

tasks = ["backup", "compile", "test", "deploy"]
processed = []

until tasks.empty?
  task = tasks.shift
  next if task == "compile"
  processed << task
end

puts "Processed: #{processed.join(', ')}"

Output:

Processed: backup, test, deploy

This is closer to real-world usage: tasks acts like a work queue. until tasks.empty? keeps the loop running as long as there’s work left. Array#shift removes and returns the first element each time, mutating tasks in place, so the loop naturally terminates once the array is drained. next skips the rest of the current iteration (here, skipping the append for "compile") without breaking out of the loop entirely.

How It Works Step by Step

Every pass through a while or until loop follows the same cycle:

  • 1. Ruby evaluates the condition expression.
  • 2. For while, if the result is truthy, continue; if falsy (nil or false), exit the loop. For until, the check is inverted: continue if the result is falsy, exit if truthy.
  • 3. If continuing, run the loop body from top to bottom.
  • 4. Jump back to step 1.

Because the condition is re-checked at the very top of every cycle, nothing inside the body runs “for free” — if a variable used in the condition is never updated inside the body, the loop either never runs or never stops.

The begin…end while exception

Ruby has one special case where the condition is checked after the body instead of before: wrapping the body in begin...end and appending while or until. This guarantees the body executes at least once, which is useful for things like “try an operation, then keep retrying until it succeeds.”

input = 0

begin
  input += 1
  puts "Attempt #{input}"
end while input < 3

Output:

Attempt 1
Attempt 2
Attempt 3

The body runs first unconditionally, printing Attempt 1, and only afterward does Ruby check input < 3. Compare this to a plain while input < 3 loop placed before any increment — that would behave identically here since input starts below 3, but if input had started at 10, the begin...end while version would still run once, while a plain while loop would skip the body entirely.

Common Mistakes

Mistake 1: Forgetting to update the loop variable

The single most common way to write an infinite loop is checking a condition that the body never actually changes:

i = 1
while i <= 5
  puts i
end

This never terminates: i stays 1 forever because nothing inside the body updates it, so i <= 5 is always true. The fix is to make sure every path through the body moves the loop toward its exit condition:

i = 1
while i <= 5
  puts i
  i += 1
end

Output:

1
2
3
4
5

Mistake 2: Assuming the loop returns its last computed value

Because almost everything in Ruby evaluates to something, it’s tempting to assume a while loop “returns” whatever it last computed. It doesn’t — a while or until loop always evaluates to nil when it ends normally:

count = 0
result = while count < 5
  count += 1
end
puts "Result: #{result.inspect}"

Output:

Result: nil

Even though count ends up as 5 inside the loop, result is nil because the loop itself doesn’t hand back a value unless you tell it to with break:

count = 0
result = while count < 5
  count += 1
  break count if count == 5
end
puts "Result: #{result.inspect}"

Output:

Result: 5

break count both exits the loop immediately and makes the whole while expression evaluate to count, which is what gets assigned to result.

Mistake 3: Writing = instead of == in the condition

A single stray character turns a comparison into an assignment, and an assignment expression’s value is whatever was assigned — which is truthy unless you assigned nil or false. This produces a silent infinite loop instead of a syntax error:

count = 0
while count = 5
  puts "Looping forever because = always returns 5 (truthy)"
end

count = 5 runs on every check, always evaluates to 5 (truthy), and the loop never stops — Ruby will even print a runtime warning about this exact pattern. Use == to compare instead of = to assign:

count = 0
while count == 5
  puts "This won't print since count is 0, not 5"
end
puts "Done comparing"

Output:

Done comparing

Here count == 5 correctly compares count (which is 0) against 5, finds it false immediately, and skips the body entirely.

Best Practices

  • Prefer until condition over while !condition — it reads as a positive statement and avoids double negatives.
  • When the number of iterations is known up front, reach for times, upto, downto, step, or each instead of hand-rolling a counter with while — they’re shorter and can’t forget to increment.
  • For a deliberate infinite loop, use loop do ... end with an explicit break rather than while true — loop also automatically rescues StopIteration, which matters when iterating enumerators.
  • Double-check the direction of your comparison (< vs <=, > vs >=) — off-by-one errors here are one of the most common loop bugs.
  • Never rely on truthiness of 0, "", or [] to end a loop — they’re all truthy in Ruby, so compare explicitly (count.zero?, str.empty?, arr.empty?).
  • Use break value when you need the loop’s result, rather than mutating a variable outside the loop purely to smuggle a value out.
  • Avoid mutating a collection you’re iterating over inside a while/until loop unless the mutation is exactly what advances the loop (like the shift pattern above) — mutating in unrelated ways mid-iteration is a frequent source of skipped or duplicated elements.

Practice Exercises

  • Write a while loop that prints every even number from 2 to 20 inclusive. (Hint: check n <= 20 and increment by 2 each time.)
  • Write an until loop that starts with a string "Ruby" and keeps appending "!" to it until its length reaches 10, then prints the final string. Expected output: Ruby!!!!!!
  • Using begin...end while, simulate rolling a die by shifting numbers off a fixed array like [3, 5, 6] one at a time, printing each roll, and stopping once you shift a 6.

Summary

  • while repeats its body as long as the condition is truthy; until repeats as long as the condition is falsy — until x is equivalent to while !x.
  • Both are pre-test loops: the condition is checked before each iteration, so the body can run zero times.
  • begin...end while/until is the one post-test form in Ruby, guaranteeing the body runs at least once.
  • A while/until loop evaluates to nil normally, or to a value you supply with break value.
  • Only nil and false are falsy in Ruby — 0, "", and [] are all truthy, so end conditions must be explicit.
  • Infinite loops almost always come from a condition variable that the body never updates, or a stray = where == was meant.
  • For known iteration counts, methods like times, upto, and each are usually more idiomatic than a manual while loop.