Back to Scaling Ruby Applications guides

An Introduction to Ruby Enumerators and the Enumerable Module

Stanley Ulili
Updated on September 10, 2025

Working with collections is a core aspect of many Ruby programs. Whether you're managing user data, filtering search results, or transforming arrays of information, Ruby's Enumerable module provides elegant methods that help make collection management straightforward and efficient. The real power of these methods lies in enumerators, which are Ruby's way of enabling lazy evaluation and controlling iteration with ease.

At the heart of Ruby's enumeration system is the Enumerable module and the Enumerator class. Once you grasp how they work together, you'll rewrite Ruby code in a more expressive and often more efficient way. The Enumerable module offers a wealth of useful methods, while enumerators let you fine-tune iteration.

In this tutorial, you'll gain a deep understanding of Ruby's enumeration system, enabling you to write cleaner, more efficient code and create your own enumerable classes.

Prerequisites

Assuming you have Ruby set up on your system, this guide provides examples that work with Ruby 2.7 and later versions. To follow along, you'll need a basic understanding of Ruby syntax, arrays, hashes, and blocks. Familiarity with object-oriented programming will also help with the custom class examples.

Understanding Ruby's enumeration system

Ruby's enumeration system consists of several interconnected parts:

  • Enumerable: A module that provides iteration methods like map, select, and reduce
  • Enumerator: A class that represents a sequence of values and controls iteration
  • each: The fundamental method that Enumerable builds upon

The relationship between these components forms the foundation of Ruby's collection processing.

Let's create our project directory and explore these concepts:

 
mkdir ruby-enumerators-tutorial && cd ruby-enumerators-tutorial

Create an app.rb file and add the following code:

app.rb
# Arrays include Enumerable
numbers = [1, 2, 3, 4, 5]
puts numbers.class.ancestors.include?(Enumerable)

# So do hashes
user = { name: "Alice", age: 30, city: "Boston" }
puts user.class.ancestors.include?(Enumerable)

# And ranges
range = (1..10)
puts range.class.ancestors.include?(Enumerable)

The code checks if Ruby’s Array, Hash, and Range classes include the Enumerable module. Each check prints true, showing that these collections gain powerful iteration methods like map and select because they implement each, which Enumerable builds on.

Run the file like this:

 
ruby app.rb
Output
true
true
true

The output confirms that Array, Hash, and Range all include the Enumerable module. This means they inherit all the enumeration methods like map, select, and find. The magic happens because each of these classes implements an each method that Enumerable uses as its foundation.

Now let's see how the Enumerable module builds on the each method to provide its collection of useful methods.

How Enumerable works under the hood

The Enumerable module is built around a simple contract: any class that includes Enumerable must implement an each method. Once you have each, you get dozens of enumeration methods for free:

app.rb
numbers = [1, 2, 3, 4, 5]

# Basic iteration with each
numbers.each { |n| puts n }

# Map transforms each element
doubled = numbers.map { |n| n * 2 }
puts doubled.inspect

# Select filters elements
evens = numbers.select { |n| n.even? }
puts evens.inspect

# Reduce combines elements
sum = numbers.reduce(0) { |total, n| total + n }
puts sum

The foundational method that makes everything else possible is each, which yields each element to a block. Enumerable uses this pattern internally to implement map, select, reduce, and many other methods. When you call map, Ruby internally calls each on your collection and builds a new array with the transformed values.

This example demonstrates how these different enumeration methods transform or filter the original array, each building on the same each iteration to produce different results.

 
ruby app.rb
Output
1
2
3
4
5
[2, 4, 6, 8, 10]
[2, 4]
15

The output demonstrates how different enumeration methods transform or filter the original array.

Understanding this foundation helps you write more efficient code and create your own enumerable classes. Let's explore enumerators, which give you even more control over iteration.

Creating and using enumerators

Enumerators represent a sequence of values and give you explicit control over iteration. You can create them in several ways:

app.rb
# Create an enumerator from an array
numbers = [1, 2, 3, 4, 5]
enum = numbers.each
puts enum.class

# Call enumeration methods don't provide a block
mapped_enum = numbers.map
puts mapped_enum.class

# Create enumerators manually
manual_enum = Enumerator.new do |yielder|
  yielder << "first"
  yielder << "second" 
  yielder << "third"
end

puts manual_enum.to_a.inspect

The most common way to create an enumerator is to call an enumeration method like each or map without providing a block.

When you do this, Ruby returns an Enumerator object instead of iterating immediately. The manual enumerator creation shows how to build custom sequences using the yielder object to produce values. This code confirms that calling these methods without blocks returns Enumerator objects and that the manual enumerator produces the array we specified.

Run the file:

 
ruby app.rb
Output
Enumerator
Enumerator
["first", "second", "third"]

The output confirms that calling enumeration methods without blocks returns Enumerator objects. The manual enumerator produces the array we specified by yielding three string values.

Enumerators become particularly useful when you need to control iteration manually or work with infinite sequences.

Manual iteration with enumerators

Enumerators give you precise control over when and how iteration happens:

app.rb
numbers = [1, 2, 3, 4, 5]
enum = numbers.each

# Manual iteration with next
puts enum.next
puts enum.next
puts enum.next

# Restart iteration
enum.rewind
puts "After rewind: #{enum.next}"

# Peek at next value without consuming it
enum.rewind
puts "Peek: #{enum.peek}"
puts "Next: #{enum.next}"
puts "Peek again: #{enum.peek}"

This snippet demonstrates step-by-step iteration using the next method. Each call to next advances the enumerator to the next element and returns that value.

The rewind method resets the enumerator to the beginning, which is essential for reusing enumerators. The peek method lets you see the next value without advancing the enumerator position.

After calling next three times, rewind resets the enumerator to the first element. peek shows the next value (1) without consuming it. Still, after calling next again, peek shows the following value (2).

 
ruby app.rb
Output
1
2
3
After rewind: 1
Peek: 1
Next: 1
Peek again: 2

The output shows how manual iteration works. After calling next three times, rewind resets us back to the first element. The peek method shows the next value (1) without consuming it, but after calling next, peeking shows the following value (2).

This manual control becomes invaluable when you need to process collections in ways that don't fit the standard enumeration patterns. Let's explore how to create infinite sequences with enumerators.

Creating infinite enumerators

Enumerators can represent infinite sequences, which is particularly useful for mathematical calculations or generating test data:

app.rb
# Infinite counter
counter = Enumerator.new do |yielder|
  n = 0
  loop do
    yielder << n
    n += 1
  end
end

# Take just the first 5 values
puts counter.take(5).inspect

# Fibonacci sequence
fibonacci = Enumerator.new do |yielder|
  a, b = 0, 1
  loop do
    yielder << a
    a, b = b, a + b
  end
end

puts fibonacci.take(10).inspect

The counter enumerator creates an infinite sequence that yields incrementing integers forever, using a loop do construct. The call to counter.take(5) demonstrates how to safely work with infinite enumerators by taking only a finite number of values.

The fibonacci enumerator shows a more complex infinite pattern using multiple variables that update with each iteration. Even though both enumerators can generate unlimited values, take(n) safely extracts just the number you need without consuming infinite memory or time.

 
ruby app.rb
Output
[0, 1, 2, 3, 4]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

The output shows how infinite enumerators work in practice. Even though the counter and Fibonacci enumerators can generate unlimited values, take(n) safely extracts just the number you need without consuming infinite memory or time.

While infinite enumerators are fascinating, you'll more commonly use chaining to combine multiple enumeration operations.

Chaining enumeration methods

One of Ruby's most elegant aspects is how enumeration methods chain together naturally:

app.rb
words = ["hello", "world", "ruby", "programming", "awesome"]

# Chain multiple operations
result = words
  .select { |word| word.length > 4 }
  .map { |word| word.upcase }
  .sort

puts result.inspect

# More complex chaining
users = [
  { name: "Alice", age: 30, active: true },
  { name: "Bob", age: 25, active: false },
  { name: "Charlie", age: 35, active: true },
  { name: "Diana", age: 28, active: true }
]

active_adult_names = users
  .select { |user| user[:active] }
  .select { |user| user[:age] >= 30 }
  .map { |user| user[:name] }
  .sort

puts active_adult_names.inspect

The first chain of operations on the words array demonstrates method chaining with proper formatting, where each line represents one transformation step.

The second chain on the users array shows how the same pattern works with more complex data structures. The first chain filters words longer than 4 characters, converts them to uppercase, and sorts them alphabetically. The second chain identifies active users aged 30 or older, extracts their names, and sorts them.

 
ruby app.rb
Output
["AWESOME", "HELLO", "PROGRAMMING", "WORLD"]
["Alice", "Charlie"]

The output shows the results of chaining operations. The first chain filters words longer than 4 characters, converts them to uppercase, and sorts alphabetically. The second chain finds active users who are 30 or older, extracts their names, and sorts them.

While method chaining is convenient and readable, it can become inefficient with large datasets because each method creates intermediate arrays. This is where lazy evaluation becomes valuable.

Implementing Enumerable in custom classes

You can make your own classes enumerable by including the Enumerable module and implementing the each method:

app.rb
class Playlist
  include Enumerable

  def initialize
    @songs = []
  end

  def add_song(title, artist)
    @songs << { title: title, artist: artist }
  end

  # Required method for Enumerable
  def each
    @songs.each { |song| yield(song) }
  end

  # Custom methods can use Enumerable methods
  def by_artist(artist_name)
    select { |song| song[:artist] == artist_name }
  end
end

# Create and populate a playlist
playlist = Playlist.new
playlist.add_song("Bohemian Rhapsody", "Queen")
playlist.add_song("Hotel California", "Eagles")
playlist.add_song("Somebody to Love", "Queen")

# Use inherited Enumerable methods
puts "All songs:"
playlist.each { |song| puts "#{song[:title]} by #{song[:artist]}" }

puts "\nQueen songs:"
playlist.by_artist("Queen").each { |song| puts song[:title] }

puts "\nSong count: #{playlist.count}"

The include Enumerable statement gives the Playlist class access to all enumeration methods. The each method implementation is required and works by delegating to the internal array's each method.

The custom by_artist method shows how you can create methods that use the inherited enumeration methods. Once each is defined, methods like select, count, and map work automatically, transforming your custom class into a fully enumerable object.

 
ruby app.rb
Output
All songs:
Bohemian Rhapsody by Queen
Hotel California by Eagles
Somebody to Love by Queen

Queen songs:
Bohemian Rhapsody
Somebody to Love

Song count: 3

The output demonstrates how including Enumerable transforms your custom class. The each method works as expected, the custom by_artist method uses select internally, and methods like count work without any additional implementation.

Final thoughts

Ruby's enumeration system represents one of the language's greatest strengths. The Enumerable module provides a consistent, expressive way to work with collections, while enumerators offer precise control when you need it. Understanding these tools deeply will make you a more effective Ruby programmer.

The key principles to remember are: use method chaining for readability, consider lazy evaluation for large datasets, implement Enumerable in your custom classes when appropriate, and choose the right tool for each situation. To continue your learning, explore Ruby's official Enumerable documentation and Enumerator documentation for complete method references and advanced examples.

Got an article suggestion? Let us know
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.