Blocks and Iterators in Ruby
Ruby blocks and iterators are fundamental to writing idiomatic Ruby code. They’re what make Ruby feel natural and expressive. In this tutorial, you’ll learn what blocks are, how to use them with iterators, and how to create your own methods that accept blocks using yield.
What Are Blocks?
A block is a chunk of code you can pass to a method. It’s not an object itself, but it can be associated with a method call. Blocks are the foundation of Ruby’s iterator methods.
# A block attached to the `each` method
[1, 2, 3].each do |number|
puts "Number: #{number}"
end
Blocks can be written with do...end or with curly braces:
# Both are equivalent
[1, 2, 3].each { |number| puts "Number: #{number}" }
The do...end syntax is preferred for multi-line blocks, while curly braces work well for single-line blocks.
Using Built-in Iterators
Ruby provides many iterator methods on arrays, hashes, and other enumerables. These are what make Ruby so expressive.
The each Method
The most fundamental iterator is each. It yields each element to your block:
fruits = ["apple", "banana", "cherry"]
fruits.each do |fruit|
puts "I love #{fruit}"
end
# Output:
# I love apple
# I love banana
# I love cherry
For hashes, each yields key-value pairs:
person = { name: "Alice", age: 30, city: "London" }
person.each do |key, value|
puts "#{key}: #{value}"
end
The map Method
map transforms each element and returns a new array:
numbers = [1, 2, 3, 4, 5]
squared = numbers.map { |n| n ** 2 }
puts squared # => [1, 4, 9, 16, 25]
This is one of Ruby’s most powerful methods for data transformation.
The select and reject Methods
Filter elements based on a condition:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = numbers.select { |n| n.even? }
puts evens # => [2, 4, 6, 8, 10]
odds = numbers.reject { |n| n.even? }
puts odds # => [1, 3, 5, 7, 9]
The reduce Method
reduce combines all elements into a single value:
numbers = [1, 2, 3, 4, 5]
sum = numbers.reduce(0) { |acc, n| acc + n }
puts sum # => 15
# Or using the shorthand :+ symbol
sum = numbers.reduce(:+)
puts sum # => 15
Creating Methods with Blocks Using yield
You can create your own methods that accept blocks using the yield keyword:
def greet
puts "Before yield"
yield
puts "After yield"
end
greet { puts "Inside the block!" }
# Output:
# Before yield
# Inside the block!
# After yield
Passing Data to Blocks
You can pass data to the block using arguments after yield:
def double_numbers(numbers)
numbers.map { |n| yield n }
end
result = double_numbers([1, 2, 3, 4]) { |n| n * 2 }
puts result # => [2, 4, 6, 8]
Checking if a Block Was Given
Use block_given? to check whether a block was provided:
def maybe_increment(number)
if block_given?
yield(number)
else
number + 1
end
end
puts maybe_increment(5) # => 6
puts maybe_increment(5) { |n| n * 3 } # => 15
Common Iterator Patterns
Chaining Methods
Ruby’s iterators can be chained for powerful data processing:
result = (1..20)
.select(&:odd?)
.map { |n| n ** 2 }
.first(5)
puts result # => [1, 9, 25, 49, 81]
Iterating with Index
Use each_with_index when you need the index:
fruits = ["apple", "banana", "cherry"]
fruits.each_with_index do |fruit, index|
puts "#{index + 1}. #{fruit}"
end
# Output:
# 1. apple
# 2. banana
# 3. cherry
Range Iterators
Ruby ranges support iteration directly:
# Inclusive range
(1..5).each { |n| print "#{n} " }
# Output: 1 2 3 4 5
# Exclusive range
(1...5).each { |n| print "#{n} " }
# Output: 1 2 3 4
When to Use Blocks
Blocks are perfect for:
- Callbacks: Run code after or around a method’s main action
- Data transformation: Map arrays to new values
- Filtering: Select or reject elements based on conditions
- Accumulation: Reduce collections to single values
- Resource management: Ensure cleanup happens (like file handling)
Summary
Blocks and iterators are what make Ruby feel like Ruby. You now understand:
- What blocks are and how to write them
- Common built-in iterators:
each,map,select,reject,reduce - How to create your own methods with
yield - How to check for blocks with
block_given? - How to chain iterator methods for expressive data processing
Practice using these methods with your own data. The more you use them, the more natural they’ll feel.