Working with Ruby control flow: if, unless, and loops
Control flow is the backbone of any program, and working with Ruby’s conditionals and loops is one of the first skills every developer picks up. Control flow determines which code runs, how many times it runs, and in what order. In this tutorial, you will learn how to make decisions with conditionals and repeat actions with loops in Ruby.
Intro Context
Control flow is one of the first things that makes Ruby feel like a real programming language instead of a syntax exercise. Once you can branch on conditions and repeat work safely, you can build scripts that respond to changing input instead of running the same code every time. That is why this topic sits near the start of most Ruby learning paths.
The patterns here also show up in almost every other tutorial in this series. Methods often return early with if. Collections often use iterator methods like each and map. Error handling often depends on a branch that checks whether something worked. If you understand control flow well, the rest of Ruby becomes easier to read because you can trace what the code is doing step by step.
If you want a natural follow-up after this tutorial, keep Ruby methods and Ruby error handling handy. Those topics build directly on the branching and looping patterns you are about to see.
By the end of this guide, you will be comfortable using if, unless, case, while, until, and Ruby’s iterator methods. You will also have a feel for when a loop is the right tool and when an iterator method makes the code easier to follow.
The if statement
The if statement is the primary way Ruby programs make decisions. It executes code only when a condition evaluates to true.
This is the most common branching tool in Ruby because it reads cleanly in almost every context. When you are deciding whether to print output, skip a task, or choose between two paths, if is the simplest way to make that choice visible in the code.
temperature = 25
if temperature > 30
puts "It's hot outside!"
elsif temperature > 20
puts "It's a nice day"
elsif temperature > 10
puts "It's a bit chilly"
else
puts "It's cold outside"
end
The condition does not need parentheses, though you can use them for complex expressions. Ruby treats everything except nil and false as truthy.
The truthy rule matters because it means Ruby code often reads more like plain English. You do not need to compare against true explicitly in most cases. Instead, you check whether a value exists or whether a comparison succeeded, and Ruby handles the rest.
One-Line if
Ruby lets you write simple conditionals on a single line:
Single-line conditionals are best for very short branches. They are useful when the action is obvious and the condition is already easy to read. If the branch grows beyond one line, the longer version is usually better because it is easier to scan.
puts "You can vote!" if age >= 18
This is called a modifier and is useful for short, simple conditions.
The modifier form shows up a lot in Ruby code because it keeps guard clauses compact. You can use it when the result should be obvious at a glance, but you should avoid squeezing too much logic into the same line just because Ruby allows it.
The unless statement
unless is the opposite of if. It executes code when the condition is false.
unless can make negative checks read more naturally, especially when the code is describing a failure case or a guard clause. That said, it is still worth choosing the form that reads cleanest to you and your team. A clear if is better than a confusing unless.
password = "secret123"
unless password.length >= 8
puts "Password is too short"
end
You can also use unless with else, though it reads less naturally:
The else branch works, but it can make the sentence feel backwards. When you need a positive and a negative branch, many Rubyists switch back to if because the final code tends to read more clearly.
unless user.active?
puts "Please activate your account"
else
puts "Welcome back!"
end
When to Use unless
Use unless when checking for negative conditions or validating input. It makes your code read more naturally than nested if !condition statements.
That advice is especially useful in guard clauses. If the code should stop unless a requirement is met, unless can keep the intent compact and readable. If the condition becomes complicated, a plain if is often easier to reason about.
The case statement
When you need to compare one value against multiple options, case is cleaner than multiple elsif branches.
This is the branch structure to reach for when one value drives the choice. It keeps related options grouped together and avoids a long chain of repeated comparisons. That makes the code easier to extend when a new branch appears later.
grade = "B"
case grade
when "A"
puts "Excellent!"
when "B"
puts "Good job!"
when "C"
puts "Passed"
when "D", "F" # Multiple values in one when clause
puts "Need improvement"
else
puts "Invalid grade"
end
Case with Ranges
You can use ranges in when clauses for numeric comparisons:
Ranges are handy when the values naturally fall into bands, such as grades, scores, or age brackets. They keep the rules easy to read because the boundaries stay close to the branch they affect.
score = 85
case score
when 90..100
puts "A"
when 80..89
puts "B"
when 70..79
puts "C"
else
puts "Need improvement"
end
Case as an Expression
Unlike some languages, case in Ruby returns a value you can assign:
That expression form is useful when the branch is really just producing a label or a status string. Instead of setting a variable in each branch, you let the entire case expression produce the value directly.
status = case http_code
when 200 then "OK"
when 404 then "Not Found"
when 500 then "Server Error"
else "Unknown"
end
While loops
The while loop repeats as long as its condition is true:
Traditional loops are most useful when you do not already have a collection to iterate over. They are common in scripts, parsers, and small utilities where you need to keep working until a counter changes, a file ends, or a condition becomes false.
count = 1
while count <= 5
puts "Count: #{count}"
count += 1
end
Be careful with infinite loops — always ensure the condition will eventually become false.
That warning matters because the loop body is easy to read but just as easy to forget to update. If the condition never changes, the loop keeps going. A good habit is to check whether the body contains the code that advances the loop toward its exit point.
Until Loops
until is the opposite of while — it repeats until the condition is true:
until can read more naturally when you are describing the stop condition instead of the continue condition. Some developers prefer it for countdowns or waiting loops because the sentence sounds closer to the business rule.
count = 1
until count > 5
puts "Count: #{count}"
count += 1
end
Choose whichever reads more naturally for your specific condition.
The most important thing is not the keyword itself but the clarity of the condition. If the loop is easy to understand, the code is doing its job. If the loop needs mental translation every time you read it, try the other form and see whether the sentence becomes simpler.
Iterator methods
Ruby excels at iteration. Instead of traditional loops, Rubyists often use iterator methods.
Iterator methods are one of the biggest reasons Ruby feels expressive. They let you describe what you want to do with each element instead of writing a lot of manual loop management. That usually makes the code shorter, less error-prone, and more in line with the rest of the language.
times Iterator
The times method runs a block a specific number of times:
times is a good fit when the number of repetitions is fixed and small. It is often used for retries, repeated output, or simple counters where the loop variable matters less than the number of iterations.
5.times do |i|
puts "Iteration #{i + 1}"
end
# Or in one line:
5.times { |i| puts "Iteration #{i + 1}" }
each Iterator
The each method iterates over each element in a collection:
each is the workhorse of Ruby iteration because it keeps the collection visible in the code. When you read it, you can see both the data and the action without digging into index arithmetic.
fruits = ["apple", "banana", "cherry"]
fruits.each do |fruit|
puts "I like #{fruit}"
end
You also get the index with each_with_index:
The index is useful when the position matters, such as numbering output or matching an item to its place in a list. If you only need the value, plain each stays a little cleaner.
fruits.each_with_index do |fruit, index|
puts "#{index + 1}. #{fruit}"
end
upto and downto
For numeric ranges, upto and downto are useful:
These helpers keep numeric loops compact. They are especially helpful when the range itself is the message you want to express, because the code reads like a sequence rather than a counter management routine.
1.upto(5) { |n| puts n }
5.downto(1) { |n| puts n }
Breaking and skipping
break
The break keyword exits a loop immediately:
break is best when the loop has found what it needs or when continuing would waste time. You will often see it in search loops and validation code where the first successful match should stop the iteration.
numbers = [1, 2, 3, 4, 5]
numbers.each do |n|
break if n == 3
puts n
end
# Output: 1, 2
next
The next keyword skips to the next iteration:
next is the opposite of break. It is useful when one item in the list should be ignored but the rest still matter. That makes it a clean fit for filtering inside a loop without reshaping the collection first.
numbers = [1, 2, 3, 4, 5]
numbers.each do |n|
next if n.odd?
puts n
end
# Output: 2, 4
When to use what
- Use
iffor most conditional logic - Use
unlessfor validation and negative conditions - Use
casewhen comparing one value against multiple options - Use
while/untilwhen you need traditional loop behavior - Use iterator methods (
each,times) when working with collections
Summary
You have learned the core control flow structures in Ruby:
if— Execute code based on a conditionunless— Execute code when a condition is falsecase— Compare one value against multiple optionswhile— Loop while a condition is trueuntil— Loop until a condition is true- Iterators —
times,each,upto,downtofor clean iteration breakandnext— Control loop flow
Practice these concepts to become comfortable with Ruby’s expressive control flow. In the next tutorial, you will learn about methods — how to define and call your own reusable blocks of code.
Forward Link
The next tutorial in the Ruby fundamentals sequence covers methods, which are the natural next step after branching and looping. Once you understand how Ruby decides what to run, methods show you how to package that decision-making into reusable units that make larger programs easier to manage.
See Also
- Ruby Methods — defining and calling reusable blocks of code
- Ruby Error Handling — begin, rescue, and exception patterns
- Ruby Arrays — collections that work naturally with iterators