Kernel#loop
loop { block } loop is a built-in Kernel method that executes a block indefinitely until explicitly terminated with break. It’s useful when you need an infinite loop and want full control over when to exit.
Syntax
loop do
# code to execute repeatedly
end
Parameters
loop takes no parameters. It accepts a block that will be executed repeatedly.
Examples
Basic infinite loop
The simplest form of loop runs a counter that increments until a threshold is reached. The break keyword is the only way to exit, so every loop block must include at least one break path or the program will never finish.
counter = 0
loop do
counter += 1
puts counter
break if counter >= 5
end
# Output:
# 1
# 2
# 3
# 4
# 5
Using next to skip iterations
The next keyword skips the rest of the current iteration and jumps back to the top of the loop. Combining next with a guard clause keeps the loop body focused on the work that matters, while the skipped iterations fall away silently.
counter = 0
loop do
counter += 1
next if counter.odd?
puts counter
break if counter >= 10
end
# Output:
# 2
# 4
# 6
# 8
# 10
Raising and rescuing to simulate retry logic
Retry logic is a strong use case for loop because the number of attempts is unknown at the start. The loop keeps going until either success or a hard failure, and the rescue block tracks how many tries have been made so far.
attempts = 0
loop do
attempts += 1
begin
# Simulate a flaky operation
result = rand(1..10)
if result < 7
raise "Failed attempt"
end
puts "Success on attempt #{attempts}!"
break
rescue => e
puts "Attempt #{attempts} failed: #{e.message}"
raise if attempts >= 3
end
end
Common Patterns
Generating sequences
loop works well for sequences where the stop condition depends on the generated values themselves. Fibonacci is the classic example — you keep computing until the next term crosses a boundary, and the loop handles the repetition while your logic manages the exit.
# Generate Fibonacci numbers
a, b = 0, 1
loop do
puts a
a, b = b, a + b
break if a > 1000
end
Waiting for a condition
Polling an external resource or waiting for a flag to change is another natural fit for loop. The block keeps checking until the condition flips, and the loop exits with whatever value you pass to break. This pattern is common in integration tests and service startup checks.
require 'timeout'
status = :pending
loop do
# In real code, this would check an external resource
status = :ready if rand > 0.7
break :done if status == :ready
end
Reading from a stream until exhausted
The gets method returns nil at end-of-file, which makes it a natural partner for loop. Each iteration reads one line, and the break if line.nil? guard exits cleanly when the input is consumed. This pattern is common in command-line tools that read from stdin and process each line until the stream ends.
# Read lines until EOF
loop do
line = gets
break if line.nil?
puts line.upcase
end
Errors
The two main error cases to watch for are LocalJumpError, which occurs when break is used outside any block context, and ArgumentError, which fires if you accidentally pass arguments to a method that expects none.
- LocalJumpError: If
breakis called outside a block, Ruby raises aLocalJumpError. - ArgumentError:
loopdoes not accept arguments. Passing arguments will raise anArgumentError.
keeping repeated work under control
loop is most readable when the code inside it makes the exit condition obvious. That can be a counter, a stream read, or a status flag that eventually stops the repetition. The method is powerful because it does not decide when to stop for you, but that also means the surrounding code needs to be honest about the stopping rule. A clear break path keeps the loop from feeling endless.
It is also helpful when the loop body is small enough that the repeating pattern is easy to see at a glance. If the block starts to do too many unrelated things, the repetition itself can become hard to follow. In that case the loop usually benefits from a helper method or a smaller task inside each pass.
That makes loop feel best suited to narrow jobs like polling, streaming, and retry logic. Those patterns all have a clear stop rule, even though the loop itself does not. Once the code names that stop rule well, the method becomes a useful way to keep the repeat visible without making the control flow noisy.
The method also pairs well with break values when the loop is doing a small piece of work and should return a result once the right condition appears. That can make the code feel direct: keep checking, keep running, and leave once the answer is ready. The important part is that the exit path stays obvious, so the infinite shape of the loop never hides the actual end of the task.