If there’s one thing that made me fall in love with Ruby early on, it was the moment I understood the Enumerable module properly. Before that, I was writing for loops out of habit from other languages. After it clicked, I realized Ruby had quietly given me a whole vocabulary for expressing what I wanted done with a collection, instead of manually writing how to loop through it. This article is my attempt to explain iterators and Enumerable the way I wish someone had explained them to me — from the basics of each all the way to writing your own enumerable classes and understanding what’s happening internally.
What Is an Iterator, Really?
In Ruby, an iterator is just a method that yields control back to a block, one element at a time. The classic example is each:
fruits = ["apple", "banana", "cherry"]
fruits.each do |fruit|
puts fruit
end
Output:
apple
banana
cherry
each doesn’t return anything meaningful for chaining — it returns the original array. Its whole job is running the block once per element, for side effects like printing. This is the foundation everything else in Enumerable builds on top of.
The Enumerable Module
Here’s the part that took me a while to fully appreciate: Array, Hash, Range, and many other classes don’t each implement map, select, reduce, and dozens of other methods separately. They implement one method — each — and then include Enumerable, which gives them around 50 additional methods for free, all built on top of that single each.
puts Array.ancestors.include?(Enumerable) # true
puts Hash.ancestors.include?(Enumerable) # true
puts Range.ancestors.include?(Enumerable) # true
This is one of the cleanest examples of Ruby’s design philosophy: define one primitive operation, mix in a module, and inherit a rich, expressive API.
map: Transforming Collections
map (aliased as collect) runs the block on every element and returns a new array built from the block’s return values:
numbers = [1, 2, 3, 4, 5]
squared = numbers.map { |n| n ** 2 }
puts squared.inspect
Output:
[1, 4, 9, 16, 25]
Notice numbers itself is untouched — map doesn’t mutate the original array unless you use the bang version, map!, which replaces the array’s contents in place:
numbers.map! { |n| n * 10 }
puts numbers.inspect # [10, 20, 30, 40, 50]
I generally avoid bang methods unless I have a specific reason (like avoiding an allocation in a hot loop), because mutating shared state quietly is a common source of bugs.
select and reject: Filtering Collections
select (aliased filter) keeps elements where the block returns truthy; reject keeps elements where it returns falsy — the exact inverse:
numbers = (1..20).to_a
evens = numbers.select { |n| n.even? }
odds = numbers.reject { |n| n.even? }
puts evens.inspect
puts odds.inspect
Output:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
There’s also find (aliased detect), which returns the first matching element instead of all of them:
first_multiple_of_seven = numbers.find { |n| n % 7 == 0 }
puts first_multiple_of_seven # 7
reduce / inject: Folding a Collection into One Value
reduce (aliased inject) is the one that confuses people longest, but it’s the most powerful of the bunch — it folds a whole collection down into a single accumulated value.
numbers = [1, 2, 3, 4, 5]
sum = numbers.reduce(0) { |accumulator, n| accumulator + n }
puts sum # 15
The first argument (0) is the starting value of the accumulator. Each iteration, the block’s return value becomes the new accumulator for the next iteration. You can also pass a symbol directly for simple operations, skipping the explicit block:
sum = numbers.reduce(:+)
product = numbers.reduce(1, :*)
puts sum # 15
puts product # 120
reduce isn’t limited to numbers — it’s genuinely general-purpose. Here’s building a word-frequency hash:
words = %w[ruby is fun ruby is powerful ruby is elegant]
frequency = words.reduce(Hash.new(0)) do |counts, word|
counts[word] += 1
counts
end
puts frequency.inspect
Output:
{"ruby"=>3, "is"=>3, "fun"=>1, "powerful"=>1, "elegant"=>1}
Other Enumerable Methods Worth Knowing
A handful of others I use constantly:
numbers = [5, 3, 8, 1, 9, 2]
puts numbers.sort.inspect # [1, 2, 3, 5, 8, 9]
puts numbers.sort { |a, b| b <=> a }.inspect # descending
puts numbers.min # 1
puts numbers.max # 9
puts numbers.sum # 28
puts numbers.count { |n| n > 3 } # 3
puts numbers.any? { |n| n > 8 } # true
puts numbers.all? { |n| n > 0 } # true
puts numbers.none? { |n| n > 100 } # true
puts numbers.group_by { |n| n.even? ? :even : :odd }.inspect
puts numbers.each_with_index.to_a.inspect
puts numbers.each_with_object([]) { |n, arr| arr << n * 2 }.inspect
Output:
[1, 2, 3, 5, 8, 9]
[9, 8, 5, 3, 2, 1]
1
9
28
3
true
true
true
{:odd=>[5, 3, 1, 9], :even=>[8, 2]}
[[5, 0], [3, 1], [8, 2], [1, 3], [9, 4], [2, 5]]
[10, 6, 16, 2, 18, 4]
each_with_object is my preferred alternative to reduce when the accumulator is a mutable object like an array or hash, since you don’t have to remember to return it explicitly at the end of the block.
Writing Your Own Enumerable Class
This is where Enumerable really shows its design. If you define each on your own class and mix in Enumerable, you get map, select, reduce, sort, and everything else automatically.
class Playlist
include Enumerable
def initialize
@songs = []
end
def add(song)
@songs << song
self
end
def each
return enum_for(:each) unless block_given?
@songs.each { |song| yield song }
end
end
playlist = Playlist.new
playlist.add("Bohemian Rhapsody").add("Imagine").add("Hotel California")
puts playlist.map(&:upcase).inspect
puts playlist.select { |s| s.include?("H") }.inspect
puts playlist.sort.inspect
puts playlist.count
Output:
["BOHEMIAN RHAPSODY", "IMAGINE", "HOTEL CALIFORNIA"]
["Bohemian Rhapsody", "Hotel California"]
["Bohemian Rhapsody", "Hotel California", "Imagine"]
3
I only had to write each. Every other method — map, select, sort, count, dozens more — came free from Enumerable, because internally they’re all implemented in terms of repeatedly calling each and collecting results.
Internal Working: Enumerator Objects and Lazy Evaluation
When you call an iterator method without a block, Ruby doesn’t run it immediately — it returns an Enumerator object instead:
enum = [1, 2, 3].map
puts enum.class # Enumerator
result = enum.each { |n| n * 2 }
puts result.inspect # [2, 4, 6]
This is what powers the return enum_for(:each) unless block_given? line in the Playlist example above — it lets each work correctly whether or not a block is passed, which Enumerable‘s other methods rely on internally.
Enumerators also support external iteration using next, which under the hood is implemented using Ruby Fibers — lightweight, cooperatively-scheduled coroutines that let the enumerator pause mid-iteration and resume later:
enum = [10, 20, 30].each
puts enum.next # 10
puts enum.next # 20
puts enum.next # 30
For working with very large or infinite sequences, lazy enumerators avoid computing the entire chain eagerly:
lazy_result = (1..Float::INFINITY).lazy
.select { |n| n % 3 == 0 }
.map { |n| n * 2 }
.first(5)
puts lazy_result.inspect
Output:
[6, 12, 18, 24, 30]
Without .lazy, calling select on an infinite range would hang forever trying to build the full filtered array before map even starts. .lazy chains the operations so each value flows through the whole pipeline one at a time, stopping once first(5) has what it needs.
Real-World Applications
I use Enumerable methods constantly for:
- Data transformation pipelines — parsing CSV rows, mapping them into objects, filtering by business rules, and reducing into summary statistics.
- API response processing —
mapandselecton JSON arrays returned from external services. - Report generation —
group_byandreducefor aggregating totals by category. - Custom domain collections — wrapping a database result set or a queue in a class that includes
Enumerable, so consumers get the whole familiar collection API without needing to know the underlying storage.
Best Practices
- Prefer
map/select/reduceover manual loops when you’re transforming data — it communicates intent and avoids off-by-one bugs. - Use
eachwhen you genuinely only need side effects (printing, logging) — don’t usemapand discard its return value just out of habit. - Use
each_with_objectinstead ofreducewhen accumulating into a mutable collection, for cleaner code. - Reach for
.lazywhen working with large or infinite sequences, or when you only need the first few results. - Include
Enumerablein custom classes that represent collections, rather than reinventingmap/select/sortyourself.
Common Mistakes
- Using
mapwhen you meanteach, and being surprised by an unused array ofnils being returned. - Mutating the collection you’re iterating over inside the block (e.g. calling
array.deleteinsidearray.each), which produces unpredictable skipped elements. - Forgetting that
reducewithout an initial value uses the first element as the seed, which can misbehave on empty arrays or produce a different type than expected. - Building a giant array with
select/mapon data that could have been streamed lazily, causing unnecessary memory pressure. - Implementing
eachon a custom class without handling the no-block case (enum_for), which breaks every otherEnumerablemethod that expects to be able to get anEnumeratorback.
Debugging Tips
When a chain of Enumerable calls isn’t producing what you expect, break it apart and inspect intermediate results with p after each step rather than debugging the whole chain at once. For custom Enumerable classes, test each in isolation first — if each is wrong, everything built on top of it (map, select, sort, reduce) will be wrong too, since they all delegate to it internally.
Summary
Ruby’s iterators and the Enumerable module are, in my experience, one of the best examples of good API design in any mainstream language: implement each once, mix in a module, and get dozens of expressive, well-tested collection methods for free. Learning to reach for map, select, and reduce instead of manual loops doesn’t just make code shorter — it makes intent clearer, and once you understand how Enumerable is built on each and Enumerator, you can extend that same power to your own custom classes.
References
- Ruby’s official
Enumerablemodule documentation: https://docs.ruby-lang.org/en/master/Enumerable.html - Ruby’s official
Enumeratorclass documentation: https://docs.ruby-lang.org/en/master/Enumerator.html - Ruby’s official
Arraydocumentation: https://docs.ruby-lang.org/en/master/Array.html - Ruby style guide on collections: https://rubystyle.guide/