LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 619 min read

Control Flow

Learn Ruby's if/unless/elsif, while and until loops, case/when, statement modifiers, and the difference between for and each.

Introduction

Ruby's control flow covers the familiar basics, plus a few distinctive features — unless, statement modifiers, and case/when — that consistently favor reading close to plain English.

What You Will Learn
  • Ruby's if/elsif/else syntax.
  • unless, Ruby's built-in "if not" alternative.
  • Statement modifiers — writing a condition after the code it guards.
  • while, until, case/when, and why each is generally preferred over for.

if, elsif, else

Ruby blocks end with the end keyword, similar to Lua, rather than curly braces.

score = 72
if score >= 90
puts "Grade: A"
elsif score >= 75
puts "Grade: B"
elsif score >= 60
puts "Grade: C"
else
puts "Grade: F"
end
Output

Click Run to see what this code prints.

unless: if's Negative Counterpart

unless condition runs its body when the condition is false — the direct, readable opposite of if. It's idiomatic Ruby specifically when it reads more naturally than a negated if.

logged_in = false
unless logged_in
puts "Please log in first."
end
Output

Click Run to see what this code prints.

Statement Modifiers

For a single guarded statement, Ruby lets you write the condition after the code instead of wrapping it in a full if/end block — a distinctive, very commonly used Ruby feature.

age = 15
puts "Minor" if age < 18
puts "Cannot vote" unless age >= 18
Output

Click Run to see what this code prints.

When to Use a Modifier vs a Full Block

Statement modifiers are idiomatic for a single, short guarded line — reach for a full if/end block once you have multiple statements to guard, or an elsif/else branch to express.

while and until Loops

while loops while a condition is true; until — Ruby's natural-reading counterpart — loops while a condition is false, stopping once it becomes true.

count = 1
while count <= 3
puts "while: #{count}"
count += 1
end
n = 1
until n > 3
puts "until: #{n}"
n += 1
end
Output

Click Run to see what this code prints.

case/when

case/when compares a value against several patterns in order — Ruby's answer to a switch statement, and considerably more flexible, since when clauses can match ranges, classes, and more using Ruby's === operator under the hood.

score = 72
grade = case score
when 90..100 then "A"
when 75...90 then "B"
when 60...75 then "C"
else "F"
end
puts grade
Output

Click Run to see what this code prints.

Notice grade = case ... end — a case expression evaluates to a value, so it can be assigned directly, exactly like the earlier grade method on the Student class back in this course's Introduction lesson.

for vs each

Ruby has a for loop, but idiomatic Ruby almost always prefers .each instead — it keeps loop variables properly scoped to the block and reads more consistently with Ruby's object-and-blocks style overall.

fruits = ["apple", "banana", "cherry"]
# works, but not idiomatic:
for fruit in fruits
puts fruit
end
# idiomatic Ruby:
fruits.each do |fruit|
puts fruit
end
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Reaching for for loops out of habit from another language instead of the more idiomatic .each (covered fully with blocks in lesson 8).
  • Writing unless with an else branch, or unless combined with or/and conditions — both read confusingly and are generally discouraged even though they're legal.
  • Forgetting that a statement modifier only guards the single line it's attached to, not any following lines.

Best Practices

  • Use unless only for simple, positive-reading conditions — reach for if !condition instead once logic gets more complex.
  • Use statement modifiers for short, single-line guards; use full blocks once there's more than one statement or branch.
  • Prefer .each (and other Enumerable methods, covered in lesson 10) over for loops in idiomatic Ruby code.

Frequently Asked Questions

for reuses its loop variable in the surrounding scope (which can leak past the loop), while .each cleanly scopes its variable to the block — a small but meaningful difference in a language that values predictable scoping.

Yes — when String then ... matches any value that is a String, thanks to Ruby's === operator supporting type checks as well as equality and ranges.

Functionally yes, but it exists specifically because if !condition often reads awkwardly, while unless condition usually reads naturally — a deliberate readability choice, not a functional necessity.

Key Takeaways

  • if/elsif/else and unless cover Ruby's core conditional branching.
  • Statement modifiers let you guard a single line without a full if/end block.
  • case/when is Ruby's flexible switch-statement equivalent, and evaluates to a value.
  • Idiomatic Ruby prefers .each over for loops for iterating collections.

Summary

You can now branch and loop through Ruby code idiomatically, including the readability-focused features (unless, statement modifiers) that make Ruby code distinctive. Next, you'll cover methods in depth, including Ruby's naming conventions for predicate and "bang" methods.

Next Lesson →

Methods