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.
- 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"endClick 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."endClick 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 < 18puts "Cannot vote" unless age >= 18Click Run to see what this code prints.
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 = 1while count <= 3 puts "while: #{count}" count += 1end
n = 1until n > 3 puts "until: #{n}" n += 1endClick 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 gradeClick 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 fruitend
# idiomatic Ruby:fruits.each do |fruit| puts fruitendClick Run to see what this code prints.
Common 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.