RubyLearning

Helping Ruby Programmers become Awesome!

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.