Beginner

Control Flow in Ruby

Learn conditionals, case statements, loops, and loop control in Ruby with practical Docker-ready examples

Control flow is how a program decides what to do and how many times to do it. Ruby gives you the familiar tools—if, else, loops—but wraps them in syntax that reads almost like English. As a multi-paradigm language with strong object-oriented and functional leanings, Ruby often encourages you to iterate over collections with blocks rather than write manual counter loops.

Ruby’s design philosophy of “least surprise” shines in its control flow. It adds expressive touches you won’t find everywhere: unless for negated conditions, statement modifiers that put the condition after the action, and a powerful case statement that matches on ranges, types, and patterns. Almost everything in Ruby is an expression that returns a value, so even an if or case can be assigned directly to a variable.

In this tutorial you’ll learn how to branch with if/elsif/else and unless, match values with case/when, repeat work with while/until and iterators, and control loops using break, next, and loop.

Conditionals: if, unless, and the Ternary Operator

The if/elsif/else chain is Ruby’s basic branching tool. Ruby also offers unless (an if with the condition negated) and concise statement modifiers that place the condition at the end of a line.

Create a file named control_flow.rb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Control Flow in Ruby

# if / elsif / else
temperature = 72

if temperature > 85
  puts "It's hot outside"
elsif temperature > 60
  puts "It's a pleasant day"
else
  puts "Bring a jacket"
end

# unless - reads as "if not"
logged_in = false
unless logged_in
  puts "Please log in to continue"
end

# Statement modifiers (condition comes after the expression)
puts "Access granted" if temperature > 60
puts "Warning!" unless logged_in

# Ternary operator: condition ? if_true : if_false
age = 20
status = age >= 18 ? "adult" : "minor"
puts "You are an #{status}"

Notice that if and unless blocks end with end—Ruby uses no curly braces or significant indentation for these. The ternary operator is a compact one-line alternative when you just need to pick between two values.

Case Statements: Ruby’s Powerful Switch

The case statement is far more capable than a typical C-style switch. Each when clause uses the === operator under the hood, which lets you match against ranges, classes, and more—not just literal equality. A case is also an expression, so it can return a value directly.

Create a file named case_when.rb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# case / when matching a range
grade = 85

case grade
when 90..100
  puts "Grade: A"
when 80...90   # ... excludes the upper bound (80 to 89)
  puts "Grade: B"
when 70...80
  puts "Grade: C"
else
  puts "Grade: F"
end

# case can match by type (class)
input = "hello"

case input
when Integer
  puts "Got a number"
when String
  puts "Got a string of length #{input.length}"
when Array
  puts "Got an array"
end

# case with no subject behaves like if/elsif and returns a value
hour = 14
greeting = case
           when hour < 12 then "Good morning"
           when hour < 18 then "Good afternoon"
           else "Good evening"
           end
puts greeting

The range 80...90 uses three dots to exclude its upper bound, while 80..90 (two dots) would include it. Matching on Integer and String works because a class responds to === by checking whether the value is an instance of it.

Loops: while, until, and Iterators

Ruby supports classic while and until loops, but idiomatic Ruby usually iterates over collections and ranges using blocks. Methods like each, times, and ranges replace most manual counter loops you’d write in other languages.

Create a file named loops.rb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# while loop - runs while the condition is true
count = 1
while count <= 3
  puts "while: #{count}"
  count += 1
end

# until loop - runs while the condition is false
countdown = 3
until countdown.zero?
  puts "until: #{countdown}"
  countdown -= 1
end

# each - the idiomatic Ruby way to iterate a collection
["red", "green", "blue"].each do |color|
  puts "Color: #{color}"
end

# times - repeat an action a fixed number of times
3.times { |i| puts "times: #{i}" }

# Range with each
(1..3).each { |n| puts "range: #{n}" }

Blocks can be written with do ... end (preferred for multi-line bodies) or with curly braces { ... } (preferred for one-liners). The block parameter—like |color| or |i|—receives each value in turn. Note that 3.times counts from 0 to 2.

Loop Control: break, next, and loop

Inside any loop or iterator you can change the flow: break exits immediately, while next skips to the following iteration. Ruby also has a bare loop keyword that runs forever until a break stops it.

Create a file named loop_control.rb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# break - exit the loop early
(1..10).each do |n|
  break if n > 5
  puts "break demo: #{n}"
end

# next - skip the rest of this iteration
(1..6).each do |n|
  next if n.even?
  puts "odd: #{n}"
end

# Accumulate values, stopping once a threshold is reached
sum = 0
[10, 20, 30, 40].each do |value|
  sum += value
  break if sum >= 50
end
puts "Sum stopped at: #{sum}"

# The loop keyword repeats until break is called
attempts = 0
loop do
  attempts += 1
  break if attempts == 3
end
puts "Total attempts: #{attempts}"

Here break if n > 5 reads naturally as a statement modifier, and next if n.even? skips even numbers without an explicit else. The accumulator stops at 60 because the loop only checks the threshold after adding the current value.

Running with Docker

You can run every example without installing Ruby locally by using the official Alpine image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official Ruby Alpine image
docker pull ruby:3.4-alpine

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app ruby:3.4-alpine ruby control_flow.rb

# Run the case statement example
docker run --rm -v $(pwd):/app -w /app ruby:3.4-alpine ruby case_when.rb

# Run the loops example
docker run --rm -v $(pwd):/app -w /app ruby:3.4-alpine ruby loops.rb

# Run the loop control example
docker run --rm -v $(pwd):/app -w /app ruby:3.4-alpine ruby loop_control.rb

Expected Output

Running control_flow.rb:

It's a pleasant day
Please log in to continue
Access granted
Warning!
You are an adult

Running case_when.rb:

Grade: B
Got a string of length 5
Good afternoon

Running loops.rb:

while: 1
while: 2
while: 3
until: 3
until: 2
until: 1
Color: red
Color: green
Color: blue
times: 0
times: 1
times: 2
range: 1
range: 2
range: 3

Running loop_control.rb:

break demo: 1
break demo: 2
break demo: 3
break demo: 4
break demo: 5
odd: 1
odd: 3
odd: 5
Sum stopped at: 60
Total attempts: 3

Key Concepts

  • unless is if negated — use it when a positive condition reads more clearly as “do this unless something is true,” but avoid pairing it with else for readability.
  • Statement modifiers — appending if or unless to the end of a line (puts "ok" if valid) is idiomatic Ruby for short, single-action conditions.
  • case matches with === — this lets a single case match ranges (90..100), classes (Integer, String), and more, making it far more flexible than a C-style switch.
  • Everything is an expressionif, unless, and case all return a value, so you can assign their result directly to a variable.
  • Prefer iterators over manual loopseach, times, and ranges with blocks are the idiomatic Ruby way to repeat work, while while/until remain available for condition-driven loops.
  • break and nextbreak leaves the loop entirely; next jumps to the following iteration. Both pair naturally with statement modifiers.
  • Ranges use .. and ... — two dots include the end value, three dots exclude it; this distinction matters in both case clauses and iteration.

Running Today

All examples can be run using Docker:

docker pull ruby:3.4-alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining