rubyguides

Ruby keyword: while

while expr [do] ... end

Ruby’s while keyword runs a block of code repeatedly as long as a condition stays truthy. It is a pre-test loop: the condition is checked before the body runs, which means the body may execute zero times. Unlike Array#each or other block-based iteration, while does not introduce a new scope, so any variable you assign inside the body remains visible after the loop ends.

while is one of Ruby’s built-in keywords, not a method, and you cannot override it. It has been part of the language since Ruby 1.0 and shows no signs of going away. That said, idiomatic Ruby reaches for while less often than you might expect; it shines when a loop is driven by an external condition such as a file’s sentinel line, a flag set by another thread, or an iterator-like API, instead of being used to walk through a collection.

What while does

A while loop tests its condition, runs the body if the condition is truthy, then re-tests. The loop terminates the first time the condition is false or nil. Every other value (including 0, "", and []) is truthy and keeps the loop going. If you want to exit early with a value, use break; to skip the rest of an iteration, use next; to re-run the current iteration, use redo.

a = 0

while a < 3
  puts a
  a += 1
end
# 0
# 1
# 2

a is still defined and equal to 3 after the loop exits, because while does not create a new scope.

Standard syntax

The full syntax is while expr [do] ... end. The do keyword between the condition and the body is optional; both forms parse identically.

a = 0

while a < 3 do
  puts a
  a += 1
end
# 0
# 1
# 2

You can drop the do keyword entirely and the loop behaves identically. Ruby’s grammar treats the optional do as a separator, not a structural part of the syntax, so the parser builds the same AST whether you write while cond do; body; end or while cond; body; end. Pick one form and stay consistent within a project; the Ruby Style Guide leaves the choice up to you.

a = 0

while a < 3
  puts a
  a += 1
end
# 0
# 1
# 2

The Ruby Style Guide leaves the choice to you; pick one and be consistent.

The modifier form

When the body is a single statement, you can use the modifier form: body while condition. This is the form you will see most often in idiomatic Ruby for short, single-line loops.

a = 0
a += 1 while a < 3
puts a
# 3

The modifier form is the shape you will see most often in idiomatic Ruby for short, single-line loops, especially inside DSL-style builders, configuration loaders, and polling helpers. A common pattern pairs it with sleep so the loop blocks for a fraction of a second between checks, which avoids busy-waiting while still reacting quickly once the condition flips. The loop ends on its own as soon as the test fails, with no extra bookkeeping required.

sleep 0.1 while !File.exist?("/tmp/ready.flag")

Modifier form is a statement, not an expression

The modifier form is a statement, which means you cannot use it wherever an expression is expected. Method arguments are expressions, so this raises SyntaxError:

puts(1 while false)
# SyntaxError: unexpected keyword_while

Ruby parses the outer call first, treats 1 while false as an unexpected token inside the argument list, and raises SyntaxError before any code runs. Wrapping the modifier in extra parentheses, or putting a space after the method name, gives the parser enough structure to build a valid expression. Both forms below compile cleanly, print nil, and exit without errors.

puts((1 while false))    # works
puts (1 while false)     # also works, but most editors and linters complain

Pick the extra-parentheses form when you genuinely need this pattern.

begin/end while — run the body at least once

while is a pre-test loop, so when the condition is false on entry, the body never runs. If you need the body to run at least once, wrap it in begin/end and put while after the closing end. This is Ruby’s do/while equivalent.

a = 0

begin
  a += 1
end while a < 3

puts a
# 3

This shape is useful for “do this, then keep doing it while X” patterns, like retrying an operation until it succeeds.

The return value of a while loop

A while loop returns nil unless you use break with a value. This is the same rule for loop, each, and other block-style iteration.

value = while false
  puts "body never runs"
end

value
# => nil

A while loop that finishes on its own always evaluates to nil, even when the body computed something useful along the way. To return a value from the loop, pass it to break. The argument becomes the loop’s value, the same way it does inside each and loop. This pattern is handy when a loop is searching for something and needs to hand the result back to its caller, or when the loop body is a small state machine that reports its terminal state on the way out.

a = 0

status = while (a += 1) < 100
  break :done if a == 3
end

status
# => :done

The break :done both exits the loop and supplies :done as the loop’s value. Without the argument, break still terminates the loop, but the loop’s return value stays nil.

break, next, and redo inside a while loop

break, next, and redo work the same way inside a while loop as they do inside each, loop, or any other block-based iteration.

break exits the loop entirely. Used with a value, it also sets the loop’s return value.

a = 0

while true
  a += 1
  break a if a >= 3
end

puts a
# 3

next skips the rest of the current iteration and jumps straight to the next condition check. Anything below the next call in the body does not run for that iteration, and the loop then re-tests the condition from the top. This is the right tool when most iterations are fine but a few need to be filtered out, for example skipping even numbers, ignoring malformed input, or skipping records that fail an invariant without breaking out of the whole loop.

a = 0
sum = 0

while a < 6
  a += 1
  next if a.even?
  sum += a
end

puts sum
# 9   # 1 + 3 + 5

redo re-runs the current iteration without re-checking the condition. If your body does not change something that affects the condition, redo will loop forever.

result = []

while result.length < 3
  result << result.length
  redo if result.last.even?
  result << :marker
end

p result
# [0, :marker, 1, :marker, 2, :marker]

If you find yourself reaching for while true ... break, consider Kernel#loop instead. The Style Guide recommends it because it reads as “loop forever until something breaks” without forcing the reader to parse the condition.

Common gotchas

Operator precedence with +=

while does not change Ruby’s operator precedence, so while a += 1 < 10 parses as while a += (1 < 10), which is almost never what you want. Parenthesize the test:

a = 0
a += 1 while (a += 1) < 10   # probably not what you meant either
a = 0
while (a += 1) < 10           # the form you usually want
  # ...
end

Local variable parse-order trap

while shares the same parse-order trap as if and unless modifiers: if the condition is the first place a name is seen, Ruby may parse it as a method call, not a local variable assignment, and then crash at runtime with NameError. The fix is to assign the variable somewhere obvious before the loop, or seed it with ||= so Ruby knows it is a local:

line = nil
input = []

while (line = gets) && !line.strip.empty?
  input << line.chomp
end

Variables stay in scope

A while loop does not create a new scope, unlike block-based iteration. If you write x = 1 inside the loop, x is still defined after the loop ends, even if the body never ran. This is usually what you want, but it can leak names into the surrounding code in ways that surprise people coming from C or JavaScript blocks.

redo can hang the program

redo re-runs the body without re-checking the condition. If the body does not change anything that affects the condition, the program hangs. Always make sure redo is paired with a body that mutates the state the condition tests.

Style: prefer Enumerable or Kernel#loop

The Style Guide is clear:

  • For iterating collections, use Array#each, Array#map, Array#times, Integer#upto/downto, Enumerable#each_with_index, and friends. They read more like Ruby and avoid index bookkeeping.
  • For infinite loops, use Kernel#loop { ... } rather than while true ... end. loop is a method that takes a block, so it composes nicely with break and reads as English.
  • For polling, use sleep n until condition or sleep inside a loop block.

Reach for while when the loop is driven by an external condition that is not a collection, or when you genuinely need break/next/redo semantics over a non-Enumerable sequence.

Putting it together

Here is a small, complete program that uses while for the kind of work it is actually good at: reading from an external source until a sentinel line appears. The pattern shows up in log watchers, REPLs, and command-line tooling that streams input until the user types quit or an empty line.

require "set"

seen     = Set.new
total    = 0
blank    = 0

while (line = gets)
  stripped = line.strip
  break if stripped.empty? && (blank += 1) >= 2
  break if stripped.casecmp("quit").zero?

  seen << line.chomp
  total += 1
end

puts "lines read:   #{total}"
puts "unique lines: #{seen.size}"
puts "blank lines:  #{blank}"

Save it as sentinel.rb, then run ruby sentinel.rb < input.txt and pipe in any text file. The loop reads one line at a time, accumulates unique lines in a Set, and exits as soon as it sees two blank lines in a row or the word quit on its own. Two details worth noticing:

  • The parentheses around (line = gets) matter. Without them, while line = gets still parses here, but most linters flag the bare assignment as a stylistic smell, and a future Ruby version could tighten the grammar.
  • The loop returns nil because it ends with break and no argument. Add break total at the bottom of the loop, and the script could print the final count from the last line of the program instead of from a separate puts.

That is the shape while fits: a loop driven by something outside the program, breaking out the moment the external condition changes.

Conclusion

The Ruby while keyword is the language’s pre-test loop. It tests a condition, runs the body, and re-tests until the condition is false. It supports the standard do-or-no-do block form, a single-line modifier form, and a begin/end while variant that runs the body at least once. Its return value is nil unless you use break with a value, and break/next/redo work the same way they do in any other block-based loop. while is a perfectly good tool when the loop is driven by an external condition; for collections, prefer Enumerable; for infinite loops, prefer Kernel#loop.

See also