Ruby Enumerable: The Complete Guide to Enumerable Methods
By RubyLearning
The Ruby Enumerable module is one of the most powerful and frequently used features in the language. It provides a rich collection of iteration, transformation, filtering, and aggregation methods that work across Arrays, Hashes, Ranges, and any custom class you build. This ruby enumerable tutorial covers every essential method with practical examples you can use immediately.
Whether you are looking for a ruby enumerable cheat sheet or a deep dive into lazy enumerators and custom enumerable classes, this guide has you covered. Every Ruby developer should know these ruby enumerable methods inside and out.
What is Enumerable?
Enumerable is a mixin module built into Ruby's standard library. Any class that includes Enumerable and defines an each method automatically gains access to dozens of powerful collection methods. Ruby's core classes like Array, Hash, Range, and Set all include it.
# Enumerable is a mixin module
# Any class that includes it and defines `each` gets ~50+ methods for free
[1, 2, 3].is_a?(Enumerable) # => true
(1..10).is_a?(Enumerable) # => true
{ a: 1, b: 2 }.is_a?(Enumerable) # => true
The contract is simple: include the module and implement each. Ruby does the rest.
module Enumerable
# Provides: map, select, reject, reduce, sort_by, min, max,
# any?, all?, none?, count, flat_map, zip, group_by,
# chunk, each_with_object, each_slice, each_cons,
# find, grep, tally, filter_map, sum, minmax, ...
end Core Iteration Methods
These are the foundational methods for walking through collections element by element.
each
The most basic iterator. Every Enumerable class must define each — all other Enumerable methods are built on top of it.
[10, 20, 30].each { |n| puts n }
# 10
# 20
# 30
{ name: "Ruby", year: 1995 }.each { |key, val| puts "#{key}: #{val}" }
# name: Ruby
# year: 1995 each_with_index
Yields each element along with its zero-based index.
%w[apple banana cherry].each_with_index do |fruit, i|
puts "#{i}: #{fruit}"
end
# 0: apple
# 1: banana
# 2: cherry each_with_object
Iterates while carrying a memo object through each iteration. Unlike inject, you do not need to return the accumulator at the end of the block.
# Build a hash from an array
%w[cat dog bird].each_with_object({}) do |animal, hash|
hash[animal] = animal.length
end
# => {"cat"=>3, "dog"=>3, "bird"=>4} each_slice and each_cons
each_slice splits the collection into non-overlapping chunks of a given size. each_cons yields overlapping (consecutive) groups.
# each_slice: non-overlapping chunks
(1..9).each_slice(3).to_a
# => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# each_cons: sliding window of consecutive elements
(1..5).each_cons(3).to_a
# => [[1, 2, 3], [2, 3, 4], [3, 4, 5]] Transforming Collections
These methods create new collections by transforming, reshaping, or grouping elements.
map / collect
Returns a new array with the results of running the block once for every element. map and collect are aliases.
[1, 2, 3, 4].map { |n| n ** 2 }
# => [1, 4, 9, 16]
%w[hello world].collect(&:upcase)
# => ["HELLO", "WORLD"] flat_map
Like map followed by flatten(1). Useful when the block returns arrays and you want a single flat result.
[[1, 2], [3, 4], [5]].flat_map { |arr| arr.map { |n| n * 10 } }
# => [10, 20, 30, 40, 50]
# Common use: fetching nested associations
# users.flat_map(&:orders) # all orders from all users zip
Merges elements from multiple arrays into pairs (or tuples).
names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
names.zip(scores)
# => [["Alice", 95], ["Bob", 87], ["Carol", 92]]
# Convert to a hash
names.zip(scores).to_h
# => {"Alice"=>95, "Bob"=>87, "Carol"=>92} chunk
Groups consecutive elements that share the same block return value. Useful for breaking sequences into runs.
[1, 1, 2, 2, 2, 3, 1, 1].chunk { |n| n }.to_a
# => [[1, [1, 1]], [2, [2, 2, 2]], [3, [3]], [1, [1, 1]]]
# Practical: group log lines by severity
log_lines = ["INFO: started", "INFO: loaded", "ERROR: timeout", "ERROR: retry", "INFO: done"]
log_lines.chunk { |line| line.split(":").first }.each do |severity, lines|
puts "#{severity} (#{lines.size} entries)"
end
# INFO (2 entries)
# ERROR (2 entries)
# INFO (1 entries) group_by
Groups all elements (not just consecutive ones) into a hash keyed by the block's return value.
(1..10).group_by { |n| n % 3 }
# => {1=>[1, 4, 7, 10], 2=>[2, 5, 8], 0=>[3, 6, 9]}
words = %w[apple avocado banana blueberry cherry]
words.group_by { |w| w[0] }
# => {"a"=>["apple", "avocado"], "b"=>["banana", "blueberry"], "c"=>["cherry"]} Filtering Collections
These methods narrow down a collection based on conditions.
select / filter
Returns elements for which the block returns a truthy value. select and filter are aliases.
[1, 2, 3, 4, 5, 6].select(&:even?)
# => [2, 4, 6]
# filter is an alias added in Ruby 2.6
[1, 2, 3, 4, 5, 6].filter { |n| n > 3 }
# => [4, 5, 6] reject
The inverse of select — returns elements for which the block returns false or nil.
[1, 2, 3, 4, 5, 6].reject(&:odd?)
# => [2, 4, 6] find / detect
Returns the first element matching the condition, or nil if none match. find and detect are aliases.
[7, 14, 21, 28].find { |n| n > 15 }
# => 21
%w[ruby python go].detect { |lang| lang.length == 2 }
# => "go" find_index
Returns the index of the first matching element.
%w[mon tue wed thu fri].find_index("wed")
# => 2
[10, 20, 30, 40].find_index { |n| n >= 25 }
# => 2 grep
Selects elements matching a pattern using the === operator. Works with regular expressions, classes, ranges, and more.
%w[ruby rails rack rspec sinatra].grep(/^r/)
# => ["ruby", "rails", "rack", "rspec"]
[1, "two", 3, "four", 5].grep(String)
# => ["two", "four"]
(1..100).grep(20..30)
# => [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] compact (Array)
While compact is technically an Array method (not Enumerable), it is used so frequently in method chains that it belongs in every ruby enumerable cheat sheet. It removes nil values.
[1, nil, 2, nil, 3].compact
# => [1, 2, 3]
# Often chained after map when some elements produce nil
users = ["alice", nil, "bob", nil, "carol"]
users.compact.map(&:capitalize)
# => ["Alice", "Bob", "Carol"] Sorting and Finding Extremes
sort and sort_by
sort uses the <=> operator by default. sort_by is typically faster for complex comparisons because it computes the sort key once per element.
%w[banana apple cherry].sort
# => ["apple", "banana", "cherry"]
# sort_by is preferred for complex sorts
%w[banana apple cherry].sort_by { |fruit| fruit.length }
# => ["apple", "banana", "cherry"]
# Multi-key sort
people = [{ name: "Bob", age: 30 }, { name: "Alice", age: 25 }, { name: "Carol", age: 25 }]
people.sort_by { |p| [p[:age], p[:name]] }
# => [{name: "Alice", age: 25}, {name: "Carol", age: 25}, {name: "Bob", age: 30}] min, max, min_by, max_by, minmax
Find extreme values without fully sorting the collection.
[38, 12, 97, 45].min # => 12
[38, 12, 97, 45].max # => 97
[38, 12, 97, 45].minmax # => [12, 97]
%w[ruby go python].min_by(&:length) # => "go"
%w[ruby go python].max_by(&:length) # => "python"
# Get the top 3
[38, 12, 97, 45, 83, 61].max(3) # => [97, 83, 61] Aggregating Data
count
Counts elements, optionally filtering with a block or a specific value.
[1, 2, 3, 4, 5].count # => 5
[1, 2, 2, 3, 3, 3].count(3) # => 3
[1, 2, 3, 4, 5].count(&:even?) # => 2 sum
Added in Ruby 2.4, sum adds up elements. Pass a block to transform before summing. Accepts an optional initial value (defaults to 0).
[1, 2, 3, 4, 5].sum # => 15
[1, 2, 3, 4, 5].sum { |n| n ** 2 } # => 55
# Sum with an initial value
["hello", " ", "world"].sum("") # => "hello world" reduce / inject
The most flexible aggregation method. Accumulates a single value by applying the block to each element. reduce and inject are aliases.
# Sum (explicit form)
[1, 2, 3, 4, 5].reduce(0) { |sum, n| sum + n }
# => 15
# Product
[1, 2, 3, 4, 5].inject(:*)
# => 120
# Build a frequency hash
words = %w[apple banana apple cherry banana apple]
words.reduce(Hash.new(0)) { |counts, word| counts[word] += 1; counts }
# => {"apple"=>3, "banana"=>2, "cherry"=>1} tally
Added in Ruby 2.7, tally counts occurrences of each element and returns a hash. It replaces the common reduce frequency-counting pattern shown above.
%w[apple banana apple cherry banana apple].tally
# => {"apple"=>3, "banana"=>2, "cherry"=>1}
# Ruby 3.1+ tally accepts a hash to merge into
existing = { "apple" => 10 }
%w[apple banana].tally(existing)
# => {"apple"=>11, "banana"=>1} Boolean Query Methods
These methods answer yes-or-no questions about a collection.
[2, 4, 6].all?(&:even?) # => true
[1, 2, 3].any?(&:even?) # => true
[1, 3, 5].none?(&:even?) # => true
[1, 2, 3].include?(2) # => true
[1, 2, 3].member?(4) # => false
# With no block, any? checks for truthy elements
[nil, nil, false].any? # => false
[nil, nil, 1].any? # => true
# Pattern matching with all?/any?/none? (Ruby 2.5+)
[1, 2, 3].all?(Integer) # => true
%w[ruby rails rack].any?(/^ra/) # => true Lazy Enumerators
By default, Enumerable methods are eager — they process the entire collection and build intermediate arrays at each step. The lazy method returns a Enumerator::Lazy that chains operations without creating intermediate arrays. Elements are processed one at a time, on demand.
# Eager: creates 3 intermediate arrays
(1..Float::INFINITY)
.select { |n| n.odd? } # hangs! tries to process infinite range
.map { |n| n ** 2 }
.first(5)
# Lazy: processes elements on demand
(1..Float::INFINITY)
.lazy
.select { |n| n.odd? }
.map { |n| n ** 2 }
.first(5)
# => [1, 9, 25, 49, 81] Lazy enumerators are essential when working with infinite sequences, very large files, or any situation where you want to avoid creating large intermediate collections.
# Reading a large file lazily
# File.open("huge.log").each_line.lazy
# .select { |line| line.include?("ERROR") }
# .map { |line| line.strip }
# .first(10)
# Generating Fibonacci numbers lazily
fib = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder.yield a
a, b = b, a + b
end
end
fib.lazy.select(&:even?).first(5)
# => [0, 2, 8, 34, 144] Enumerator and Enumerator::Lazy
An Enumerator is an object that encapsulates iteration. When you call an Enumerable method without a block, you get an Enumerator back. This lets you chain operations, pass iterators around as objects, and control iteration manually with next.
# Calling without a block returns an Enumerator
enum = [1, 2, 3].each
enum.class # => Enumerator
enum.next # => 1
enum.next # => 2
enum.next # => 3
# External iteration
enum = %w[a b c].each
loop do
puts enum.next
end
# a
# b
# c Enumerator::Lazy is a subclass of Enumerator that delays computation. Converting between eager and lazy is straightforward:
# Eager to lazy
lazy_enum = (1..100).lazy.select(&:even?).map { |n| n * 10 }
lazy_enum.class # => Enumerator::Lazy
# Lazy back to eager
lazy_enum.to_a # => [20, 40, 60, ..., 1000]
lazy_enum.force # same as to_a
lazy_enum.first(3) # => [20, 40, 60] Creating Custom Enumerable Classes
To make your own class Enumerable, include the module and define each. Optionally, define <=> for sorting methods to work.
class WordCollection
include Enumerable
def initialize(text)
@words = text.split
end
def each(&block)
@words.each(&block)
end
# Define <=> to enable sort, min, max on the collection itself
def <=>(other)
@words.length <=> other.to_a.length
end
end
collection = WordCollection.new("the quick brown fox jumps over the lazy dog")
collection.count # => 9
collection.select { |w| w.length > 3 } # => ["quick", "brown", "jumps", "over", "lazy"]
collection.map(&:upcase) # => ["THE", "QUICK", "BROWN", ...]
collection.sort_by(&:length) # => ["the", "fox", "the", "dog", "over", ...]
collection.group_by(&:length) # => {3=>["the", "fox", ...], 5=>["quick", ...], ...}
collection.tally # => {"the"=>2, "quick"=>1, "brown"=>1, ...}
collection.any? { |w| w == "fox" } # => true
This is a powerful pattern. By writing just one method (each), your class inherits the full suite of Enumerable capabilities. Use it for domain objects that wrap collections: playlists, inventories, search results, and more.
Chaining Enumerable Methods for Real-World Data Processing
The true power of Enumerable shows when you chain methods together to build data processing pipelines. Each method takes input from the previous one, keeping your code declarative and readable. For teams processing large datasets or monitoring data streams, tools like IntelDaily.ai demonstrate how these enumerable-style pipelines translate into real-world data monitoring and analysis workflows.
# Real-world example: process a CSV of sales data
sales = [
{ product: "Widget", region: "US", amount: 120.50 },
{ product: "Gadget", region: "EU", amount: 89.99 },
{ product: "Widget", region: "US", amount: 340.00 },
{ product: "Gizmo", region: "US", amount: 55.00 },
{ product: "Gadget", region: "EU", amount: 199.99 },
{ product: "Widget", region: "EU", amount: 78.00 },
{ product: "Gizmo", region: "US", amount: 42.00 },
]
# Total revenue by product, sorted descending
sales
.group_by { |s| s[:product] }
.transform_values { |entries| entries.sum { |e| e[:amount] } }
.sort_by { |_product, total| -total }
# => [["Widget", 538.5], ["Gadget", 289.98], ["Gizmo", 97.0]]
# Top region by number of sales
sales
.map { |s| s[:region] }
.tally
.max_by { |_region, count| count }
# => ["US", 4]
# Products with at least $200 total sales in the US
sales
.select { |s| s[:region] == "US" }
.group_by { |s| s[:product] }
.filter_map { |product, entries|
total = entries.sum { |e| e[:amount] }
[product, total] if total >= 200
}
# => [["Widget", 460.5]] Another common pattern is processing text data:
# Word frequency analysis on a text
text = "to be or not to be that is the question to be is to ask"
text.split
.map(&:downcase)
.tally
.sort_by { |_word, count| -count }
.first(5)
# => [["to", 4], ["be", 3], ["is", 2], ["or", 1], ["not", 1]] Ruby 3+ Enumerable Additions
Ruby has continued to expand Enumerable with practical new methods. Here are the key additions from Ruby 2.7 through Ruby 3.x.
filter_map (Ruby 2.7+)
Combines filter and map in a single pass. Returns only non-nil results from the block. This eliminates the common .map { ... }.compact pattern.
# Before filter_map
[1, 2, 3, 4, 5].map { |n| n * 2 if n.odd? }.compact
# => [2, 6, 10]
# With filter_map
[1, 2, 3, 4, 5].filter_map { |n| n * 2 if n.odd? }
# => [2, 6, 10]
# Parsing data with possible failures
inputs = ["42", "hello", "99", "", "7"]
inputs.filter_map { |s| Integer(s) rescue nil }
# => [42, 99, 7] tally (Ruby 2.7+)
As shown above, tally counts element occurrences. Ruby 3.1 added the ability to pass an initial hash.
intersection, union, difference (Ruby 2.6-2.7)
While these are Array methods rather than Enumerable, they pair perfectly with enumerable chains.
a = [1, 2, 3, 4, 5]
b = [3, 4, 5, 6, 7]
a.intersection(b) # => [3, 4, 5]
a.union(b) # => [1, 2, 3, 4, 5, 6, 7]
a.difference(b) # => [1, 2]
# Also available as operators
a & b # => [3, 4, 5]
a | b # => [1, 2, 3, 4, 5, 6, 7]
a - b # => [1, 2] Enumerator::Product (Ruby 3.2+)
Enumerator::Product generates the cartesian product of multiple enumerables lazily.
# Ruby 3.2+
e = Enumerator::Product.new(1..3, ["a", "b"])
e.to_a
# => [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]] Ruby Enumerable Cheat Sheet
Here is a quick reference table of the most-used ruby enumerable methods organized by purpose:
| Purpose | Methods | Notes |
|---|---|---|
| Iterate | each, each_with_index, each_with_object, each_slice, each_cons | Foundation of all Enumerable |
| Transform | map, flat_map, zip, chunk, group_by | collect is an alias for map |
| Filter | select, reject, find, find_index, grep, filter_map | filter = select, detect = find |
| Sort | sort, sort_by, min, max, min_by, max_by, minmax | Requires <=> for sort |
| Aggregate | count, sum, reduce, tally | inject is an alias for reduce |
| Boolean | any?, all?, none?, include? | member? is an alias for include? |
| Lazy | lazy, force / to_a | Essential for infinite or very large collections |
The Enumerable module is the backbone of idiomatic Ruby. Master these methods and you will write cleaner, more expressive code with fewer loops and temporary variables. Start with map, select, and reduce, then expand your repertoire to flat_map, tally, filter_map, and lazy enumerators as your data processing needs grow.