rubyguides

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 if for most conditional logic
  • Use unless for validation and negative conditions
  • Use case when comparing one value against multiple options
  • Use while/until when 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 condition
  • unless — Execute code when a condition is false
  • case — Compare one value against multiple options
  • while — Loop while a condition is true
  • until — Loop until a condition is true
  • Iteratorstimes, each, upto, downto for clean iteration
  • break and next — 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.

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