Learn Ruby - Control Flow & Operators
Series/Learn Ruby/Episode 4
Episode 4 of 23

Learn Ruby - Control Flow & Operators

This episode covers program control flow in Ruby: if, unless, ternary, and case branching with in pattern matching, while, until, loop loops, the each, times, and upto iterators, break, next, and redo control, plus short-circuit and spaceship operators.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

Episode 3's basic syntax gave you vocabulary. Episode 4 gives you logic: the way to make a program make decisions and repeat actions. Control flow is the foundation of every meaningful program — input validation, data iteration, all the way to web application business logic, everything revolves around branching and looping.

Ruby has a distinctive style in this regard: if and unless can be written as modifiers at the end of a line, short-circuit operators are often used as a replacement for one-line if, and case doesn't only match values — it also supports pattern matching with the in keyword. Episode 4 dissects all of it thoroughly, along with comparison operators and the <=> spaceship.

Branching with if, elsif, and else

The Standard Form

The most basic branching structure in Ruby:

RubyThe if branch
nilai = 85
if nilai >= 90
  puts "Nilai A"
elsif nilai >= 75
  puts "Nilai B"
else
  puts "Nilai C"
end

Notice the elsif keyword (not elseif). if in Ruby is an expression, so it returns a value — you can write hasil = if ... end. The ruby -e 'puts "ok"{:bash}' command can be used to test these logic snippets without creating a file.

unless and Ternary

unless is the inverse of if: it executes a block when the condition is false. It's great for writing negations that are easier to read. The modifier form puts the condition at the end of the line:

Rubyunless and ternary
hujan = false
puts "Langit cerah" unless hujan
status = hujan ? "basah" : "kering"
puts status

puts "Langit cerah" unless hujan displays the text because hujan is false. The ternary operator hujan ? "basah" : "kering" evaluates a condition, then picks the left branch if true and the right branch if false. Use ternaries only for short expressions so they stay readable.

case and Pattern Matching

case compares one expression against many possibilities. Ruby 3+ adds pattern matching with in to destructure data structures:

Rubycase with when and in
kode = "GET"
case kode
when "GET" then puts "Baca data"
when "POST" then puts "Kirim data"
else puts "Metode lain"
end
 
case [1, 2]
in [Integer => a, Integer => b]
  puts "Dua angka: #{a} dan #{b}"
else
  puts "Bukan array dua angka"
end

The second case block matches the array [1, 2] against the pattern [Integer => a, Integer => b], binding a and b while validating their types. This is the power of pattern matching that a plain when doesn't have.

Loops in Ruby

while, until, and loop

Ruby provides classic loops with a slight twist. while runs while a condition is true, until runs while a condition is false, and loop runs forever until stopped by break:

RubyThe while loop
angka = 0
while angka < 3
  puts angka
  angka += 1
end

A for loop also exists, but the Ruby community rarely uses it because iterators are more idiomatic. until angka == 3 produces the same behavior as the while angka < 3 above, just written from the negation side.

The each, times, and upto Iterators

Ruby's most distinctive way is the iterator — a method that accepts a block and calls it for every element:

RubyThe times and upto iterators
3.times { |i| puts "iterasi ke-#{i}" }
1.upto(3) { |n| puts n }

3.times { |i| puts "iterasi ke-#{i}" } calls the block three times with an index starting from 0, and 1.upto(3) { |n| puts n } runs from 1 to 3. Iterators like each, map, and select will be covered in more depth in episodes 5 and 10 — these are patterns you'll encounter in nearly all Ruby code.

break, next, and redo

Three keywords control loop flow: break stops the loop, next jumps to the next iteration, and redo repeats the current iteration without re-evaluating the condition:

Rubybreak and next
(1..10).each do |n|
  next if n.even?
  break if n > 7
  puts n
end

The block above prints 1, 3, 5, 7 — even numbers are skipped by next if n.even? and the loop stops when n > 7. Notice the use of the if modifier at the end of the line: a very common style in Ruby.

Operators and Short-circuit

&&, ||, and !

The logical operators &&, ||, and ! perform short-circuit evaluation: && stops evaluating as soon as it finds false, and || stops as soon as it finds true. This is often used to set default values:

Short-circuit in the terminal
ruby -e 'nama = ENV["USER"] || "tamu"; puts nama'

If ENV["USER"] is empty or nil, the expression ENV["USER"] || "tamu" returns "tamu" because nil is falsy. This pattern is called a nil guard and is very common in Ruby code. There's also the &. form (safe navigation), which wraps a method call — if the receiver is nil, the whole expression produces nil without an error.

and, or, not and Comparisons

Ruby also provides and, or, and not — equivalents of &&, ||, and ! with lower precedence. This precedence difference is often a source of confusion; the practical rule is: use &&/|| for logic inside expressions, and and/or for chaining separate statements.

The standard comparison operators (==, !=, <, >, <=, >=) work as usual. What's distinctive is the spaceship <=>, which returns -1, 0, or 1:

The spaceship operator
ruby -e 'puts 2 <=> 5'
ruby -e 'puts [3, 1, 2].sort { |a, b| a <=> b }'

2 <=> 5 produces -1 (the left side is smaller), and this operator is very useful for sorting and for implementing the Comparable module, which will be discussed in episode 6. The -1/0/1 return values are what the sort method uses to determine ordering.

Info

Remember Ruby's truthy rule from episode 3: only nil and false are falsy. As a consequence, ENV["USER"] || "tamu" will always produce the correct fallback as long as the variable's value isn't a non-empty string — even an empty string is considered truthy.

Conclusion

Episode 4 equips you with program logic: if, elsif, unless, ternary, and case branching with in pattern matching, while, until, loop loops and the each, times, upto iterators, break, next, and redo control, plus short-circuit and spaceship operators.

Key takeaways:

  • if and case are expressions that return a value in Ruby.
  • unless executes a block when the condition is false; use it for more readable negations.
  • in pattern matching can destructure arrays and hashes at the same time.
  • The each, times, and upto iterators are more idiomatic than for.
  • && and || short-circuit and can be used as a nil guard.
  • <=> returns -1, 0, or 1 for comparison and sorting.

In the next episode, episode 5, we will discuss methods, blocks, proc, and lambda — method definitions with positional parameters, keyword arguments, and splats, implicit return values, blocks with do...end and curly braces, yield and &block, the difference between Proc and Lambda, the it block parameter, and Enumerable methods like map, select, and reduce. This is the episode that unlocks the power of Ruby's functional style.

Learn Ruby - Control Flow & Operators | Learn Ruby