Enumerable #

Almost every Ruby program you’ve ever read or written uses Enumerable — often without realizing it. When you call .map, .select, or .sort_by on an Array, you’re using methods from the Enumerable module. This module is one of Ruby’s greatest contributions to how we write code — it turns operations that would normally require explicit loops into declarative expressions that read like sentences. This article covers how Enumerable works from the inside, the most frequently used methods, chaining techniques for clean data pipelines, and lazy enumeration for large or infinite collections.

What Is Enumerable? #

Enumerable is a Ruby module providing dozens of methods for working with collections — iteration, searching, transformation, sorting, and aggregation. Its working is simple: you only need to implement one method, each, and Enumerable automatically provides all the other methods.

# Array and Hash already include Enumerable by default
[1, 2, 3].class.ancestors
# => [Array, Enumerable, Object, Kernel, BasicObject]

{ a: 1 }.class.ancestors
# => [Hash, Enumerable, Object, Kernel, BasicObject]

# You can make your own Enumerable class
class BookList
  include Enumerable

  def initialize
    @books = []
  end

  def add(book)
    @books << book
    self
  end

  # The only method that must be implemented
  def each(&block)
    @books.each(&block)
  end
end

list = BookList.new
list.add("Sapiens").add("Atomic Habits").add("Deep Work")

# After implementing each, all Enumerable methods are available
list.map(&:upcase)
# => ["SAPIENS", "ATOMIC HABITS", "DEEP WORK"]

list.select { |b| b.length > 7 }
# => ["Sapiens", "Atomic Habits", "Deep Work"]

list.sort
# => ["Atomic Habits", "Deep Work", "Sapiens"]
flowchart TD
    A[Class including Enumerable] --> B[Implement the each method]
    B --> C[Enumerable provides all other methods]
    C --> D[map / flat_map]
    C --> E[select / reject / filter]
    C --> F[reduce / inject]
    C --> G[sort / sort_by / min / max]
    C --> H[group_by / tally / chunk]
    C --> I[find / detect / any? / all? / none?]
    C --> J[each_with_object / each_with_index]
    C --> K[lazy / take / first]

The consequence of this design is very elegant: all classes that can be iterated — Array, Hash, Range, Set, or your own classes — share the same API. Learn Enumerable once, and you can use it everywhere.


Basic Iteration #

Before diving into more complex methods, it’s important to understand the basic iteration variations frequently used and their differences.

each is the foundation — it iterates a collection and executes the block for every element, then returns the original collection. each_with_index adds the index to the block. each_with_object is useful when you want to build an object during iteration.

names = ["alice", "bob", "charlie"]

# each — pure iteration, returns the original collection
names.each { |n| puts n.capitalize }
# Alice
# Bob
# Charlie

# each_with_index — when you need the element's position
names.each_with_index do |n, i|
  puts "#{i + 1}. #{n.capitalize}"
end
# 1. Alice
# 2. Bob
# 3. Charlie

# each_with_object — building a new object during iteration
result = names.each_with_object({}) do |n, hash|
  hash[n] = n.length
end
# => {"alice"=>5, "bob"=>3, "charlie"=>7}

# each_slice — process elements in groups of N
(1..10).each_slice(3) { |group| p group }
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]

# each_cons — sliding window of size N
(1..5).each_cons(3) { |window| p window }
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]
# ANTI-PATTERN: using each to build a new array
result = []
[1, 2, 3, 4, 5].each { |x| result << x * 2 }
# => [2, 4, 6, 8, 10]

# CORRECT: use map — more declarative, no state mutation
result = [1, 2, 3, 4, 5].map { |x| x * 2 }
# => [2, 4, 6, 8, 10]

Transformation with map and flat_map #

map (alias collect) is the most frequently used transformation method. It takes each element, executes the block, and returns a new array with the transformed results. The original collection is unchanged.

numbers = [1, 2, 3, 4, 5]

# map — one-to-one transformation
numbers.map { |n| n ** 2 }
# => [1, 4, 9, 16, 25]

numbers.map { |n| n.even? ? "even" : "odd" }
# => ["odd", "even", "odd", "even", "odd"]

# map with a symbol — shorthand for calling a method
["hello", "world", "ruby"].map(&:upcase)
# => ["HELLO", "WORLD", "RUBY"]

["1", "2", "3"].map(&:to_i)
# => [1, 2, 3]

# map on a Hash — iterates [key, value] pairs
{ a: 1, b: 2, c: 3 }.map { |k, v| "#{k}=#{v}" }
# => ["a=1", "b=2", "c=3"]

# If you want a Hash result, add .to_h
{ a: 1, b: 2, c: 3 }.map { |k, v| [k, v * 10] }.to_h
# => {:a=>10, :b=>20, :c=>30}

# Or use transform_values / transform_keys (Ruby 2.4+)
{ a: 1, b: 2, c: 3 }.transform_values { |v| v * 10 }
# => {:a=>10, :b=>20, :c=>30}

flat_map is useful when the block produces arrays and you want the result flattened one level.

words = ["hello world", "foo bar", "ruby enumerable"]

# ANTI-PATTERN: separate map then flatten
words.map { |w| w.split(" ") }.flatten
# => ["hello", "world", "foo", "bar", "ruby", "enumerable"]

# CORRECT: flat_map directly
words.flat_map { |w| w.split(" ") }
# => ["hello", "world", "foo", "bar", "ruby", "enumerable"]

# Practical example: collect all tags from a set of articles
articles = [
  { title: "Ruby Tips", tags: ["ruby", "programming"] },
  { title: "Web Dev", tags: ["rails", "ruby", "web"] },
  { title: "Testing", tags: ["rspec", "testing"] }
]

all_tags = articles.flat_map { |a| a[:tags] }
# => ["ruby", "programming", "rails", "ruby", "web", "rspec", "testing"]

all_tags.uniq.sort
# => ["programming", "rails", "rspec", "ruby", "testing", "web"]

Filtering with select, reject, and filter_map #

select (alias filter) returns the elements for which the block returns a truthy value. reject is the opposite — it returns elements for which the block returns a falsy value.

numbers = (1..10).to_a

# select — take those meeting the condition
numbers.select { |n| n.even? }
# => [2, 4, 6, 8, 10]

numbers.select { |n| n > 5 }
# => [6, 7, 8, 9, 10]

# reject — discard those meeting the condition
numbers.reject { |n| n.even? }
# => [1, 3, 5, 7, 9]

# On Hashes
products = { apple: 5000, mango: 15000, orange: 8000, durian: 45000 }

products.select { |name, price| price < 10_000 }
# => {:apple=>5000, :orange=>8000}

products.reject { |name, price| price > 20_000 }
# => {:apple=>5000, :mango=>15000, :orange=>8000}

filter_map is a combination of select + map in one operation — useful when you want to filter and transform simultaneously, and don’t want nil values in the result.

data = ["1", "abc", "2", nil, "3", "", "4"]

# ANTI-PATTERN: select then map, two iterations
data.select { |x| x&.match?(/\d+/) }.map(&:to_i)
# => [1, 2, 3, 4]

# CORRECT: filter_map — one iteration, cleaner
data.filter_map { |x| x&.to_i if x&.match?(/\d+/) }
# => [1, 2, 3, 4]

# Practical example: processing API responses that may contain nil
response = [
  { id: 1, value: "42" },
  { id: 2, value: nil },
  { id: 3, value: "invalid" },
  { id: 4, value: "99" }
]

valid_values = response.filter_map do |item|
  Integer(item[:value], exception: false)
end
# => [42, 99]

Searching with find, any?, all?, none?, and count #

When you need to find a specific element or check a condition on a collection, Enumerable provides expressive methods that read like human sentences.

users = [
  { name: "Alice", age: 28, active: true },
  { name: "Bob", age: 17, active: false },
  { name: "Charlie", age: 35, active: true },
  { name: "Diana", age: 22, active: true }
]

# find / detect — return the first matching element
users.find { |u| u[:age] >= 18 && u[:active] }
# => {:name=>"Alice", :age=>28, :active=>true}

# find_index — return the index of the first matching element
users.find_index { |u| u[:name] == "Charlie" }
# => 2

# any? — is there at least one match?
users.any? { |u| u[:age] < 18 }
# => true

# all? — do all match?
users.all? { |u| u[:name].length > 0 }
# => true

# none? — is there no match?
users.none? { |u| u[:age] > 100 }
# => true

# one? — is there exactly one match?
users.one? { |u| !u[:active] }
# => true

# count — count the matches
users.count { |u| u[:active] }
# => 3

users.count   # without a block: count all elements
# => 4

find returns nil when no element matches, not an empty array. If you call a method on a find result without checking, you’ll get a NoMethodError: undefined method '...' for nil. Use the safe navigation operator &. or make sure the result isn’t nil before processing further.

# ANTI-PATTERN: directly accessing a property without a nil check
admin = users.find { |u| u[:role] == "admin" }
admin[:name]   # => NoMethodError if not found!

# CORRECT: use safe navigation
admin&.fetch(:name, "not found")

Aggregation with reduce and inject #

reduce (alias inject) is the most powerful and most frequently misused method. It accumulates a value across the whole collection into a single result — suitable for summation, multiplication, string building, or constructing complex data structures.

numbers = [1, 2, 3, 4, 5]

# reduce with an initial value and a block
numbers.reduce(0) { |accumulator, n| accumulator + n }
# => 15

# reduce with a symbol — shorthand for binary operations
numbers.reduce(:+)   # => 15
numbers.reduce(:*)   # => 120
numbers.reduce(10, :+)  # with an initial value: 10 + 1 + 2 + 3 + 4 + 5 = 25

# Finding max and min
numbers.reduce { |max, n| n > max ? n : max }   # => 5
numbers.reduce { |min, n| n < min ? n : min }    # => 1
# (For these, better to use min/max directly)

# Building a Hash from an array
words = ["ruby", "python", "golang"]
words.reduce({}) { |hash, w| hash.merge(w => w.length) }
# => {"ruby"=>4, "python"=>6, "golang"=>6}

# Joining strings
["Hello", "World", "Ruby"].reduce { |result, word| "#{result} #{word}" }
# => "Hello World Ruby"
# Real example: calculating the total price from a shopping cart
cart = [
  { name: "Book", price: 75_000, quantity: 2 },
  { name: "Pen", price: 5_000, quantity: 5 },
  { name: "Bag", price: 150_000, quantity: 1 }
]

total = cart.reduce(0) { |sum, item| sum + (item[:price] * item[:quantity]) }
# => 375_000

# With the more expressive sum (Ruby 2.4+)
total = cart.sum { |item| item[:price] * item[:quantity] }
# => 375_000
# ANTI-PATTERN: reduce for tasks with dedicated methods
numbers.reduce(0) { |sum, n| sum + n }       # ✗ too verbose for summation
numbers.reduce { |min, n| n < min ? n : n }  # ✗ just use min

# CORRECT: use the more specific method when available
numbers.sum        # ✓ summation
numbers.min        # ✓ smallest value
numbers.max        # ✓ largest value
numbers.minmax     # ✓ [min, max] at once

Sorting with sort, sort_by, min, max #

Enumerable provides several ways to sort and find extreme values in a collection. Choosing the right method affects both readability and performance.

numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]

# sort — default sorting (ascending)
numbers.sort
# => [1, 1, 2, 3, 3, 4, 5, 5, 6, 9]

# sort with a custom comparator
numbers.sort { |a, b| b <=> a }   # descending
# => [9, 6, 5, 5, 4, 3, 3, 2, 1, 1]

# min and max
numbers.min   # => 1
numbers.max   # => 9
numbers.minmax # => [1, 9]

# min_by and max_by — by criteria
words = ["banana", "apple", "kiwi", "strawberry", "fig"]
words.min_by(&:length)   # => "fig"
words.max_by(&:length)   # => "strawberry"
words.minmax_by(&:length) # => ["fig", "strawberry"]

sort_by is used more often than sort with a block, because it internally uses the Schwartzian Transform algorithm — more efficient when the sorting criteria are expensive to compute.

users = [
  { name: "Charlie", score: 85 },
  { name: "Alice", score: 92 },
  { name: "Bob", score: 78 },
  { name: "Diana", score: 95 }
]

# sort_by — cleaner than sort with a block
users.sort_by { |u| u[:score] }
# => [{name:"Bob", score:78}, {name:"Charlie", score:85}, ...]

# Descending: negate the value
users.sort_by { |u| -u[:score] }
# => [{name:"Diana", score:95}, {name:"Alice", score:92}, ...]

# Multi-criteria: sort by an array
users_extended = [
  { name: "Alice", department: "Engineering", score: 85 },
  { name: "Bob", department: "Design", score: 92 },
  { name: "Charlie", department: "Engineering", score: 78 },
  { name: "Diana", department: "Design", score: 85 }
]

# Sort by department, then by score descending within the same department
users_extended.sort_by { |u| [u[:department], -u[:score]] }
# Sort by [department asc, score desc]
# ANTI-PATTERN: sort with a comparison block for simple criteria
data.sort { |a, b| a[:name] <=> b[:name] }

# CORRECT: sort_by is cleaner and more efficient
data.sort_by { |item| item[:name] }
data.sort_by(&:name)  # if name is a method/attr accessor

Grouping with group_by, tally, and chunk #

When you need to group data by specific criteria, Enumerable provides very useful methods for this case.

group_by returns a Hash where the keys are the block results and the values are arrays of elements producing those keys.

transactions = [
  { id: 1, type: "debit", amount: 50_000 },
  { id: 2, type: "credit", amount: 200_000 },
  { id: 3, type: "debit", amount: 75_000 },
  { id: 4, type: "credit", amount: 100_000 },
  { id: 5, type: "debit", amount: 30_000 }
]

# group_by — group by criteria
by_type = transactions.group_by { |t| t[:type] }
# => {
#   "debit" => [{id:1,...}, {id:3,...}, {id:5,...}],
#   "credit" => [{id:2,...}, {id:4,...}]
# }

# Calculate the total per type
by_type.transform_values { |trx| trx.sum { |t| t[:amount] } }
# => {"debit" => 155_000, "credit" => 300_000}

# group_by with a computed criterion
(1..10).group_by { |n| n % 3 }
# => {1=>[1, 4, 7, 10], 2=>[2, 5, 8], 0=>[3, 6, 9]}

# group_by to group objects by class
mixed_data = [1, "hello", 2, "world", :sym, 3, :other]
mixed_data.group_by(&:class)
# => {Integer=>[1, 2, 3], String=>["hello", "world"], Symbol=>[:sym, :other]}

tally counts how many times each element appears in a collection.

# tally — count occurrence frequencies
["apple", "mango", "apple", "orange", "mango", "apple"].tally
# => {"apple"=>3, "mango"=>2, "orange"=>1}

# Example: log level analysis
log_levels = [:info, :error, :info, :warn, :error, :info, :error]
log_levels.tally
# => {:info=>3, :error=>3, :warn=>1}

# Combined with sort_by to find the most frequent
log_levels.tally.max_by { |_, count| count }
# => [:info, 3]  or [:error, 3] — both tie

# tally_by (Ruby 3.1+) — tally with a transformation
words = ["Ruby", "ruby", "RUBY", "Python", "python"]
words.tally_by(&:downcase)
# => {"ruby"=>3, "python"=>2}

chunk groups consecutive elements having the same block value — different from group_by, which groups globally.

# chunk — group consecutive same-valued elements
[1, 1, 2, 2, 3, 1, 1].chunk { |n| n }.map { |key, arr| [key, arr.length] }
# => [[1, 2], [2, 2], [3, 1], [1, 2]]

# chunk_while — group while the condition holds
[1, 2, 4, 9, 10, 11, 12, 15, 16, 19, 20, 21].chunk_while { |i, j| j - i <= 1 }.to_a
# => [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]

# slice_when — cut the collection when the condition holds (inverse of chunk_while)
[1, 2, 4, 9, 10, 11, 12, 15].slice_when { |i, j| j - i > 1 }.to_a
# => [[1, 2], [4], [9, 10, 11, 12], [15]]

Chaining and Data Pipelines #

One of Enumerable’s greatest strengths is chaining — connecting several methods in a single expression to form a clean data pipeline.

# Scenario: from raw transaction data, take credit transactions above 50k,
# sort from largest, and fetch the customer names

transactions = [
  { customer: "Alice", type: :credit, amount: 120_000 },
  { customer: "Bob", type: :debit, amount: 75_000 },
  { customer: "Charlie", type: :credit, amount: 30_000 },
  { customer: "Diana", type: :credit, amount: 200_000 },
  { customer: "Eve", type: :debit, amount: 90_000 },
  { customer: "Frank", type: :credit, amount: 85_000 }
]

# ANTI-PATTERN: imperative with state mutation
result = []
transactions.each do |t|
  if t[:type] == :credit && t[:amount] > 50_000
    result << t
  end
end
result.sort_by! { |t| -t[:amount] }
names = result.map { |t| t[:customer] }

# CORRECT: declarative pipeline
names = transactions
  .select { |t| t[:type] == :credit && t[:amount] > 50_000 }
  .sort_by { |t| -t[:amount] }
  .map { |t| t[:customer] }
# => ["Diana", "Alice", "Frank"]

Chaining creates a pipeline where each step knows only one thing: “receive a collection, do one transformation, pass the result on.” This aligns with the Single Responsibility principle, making code easy to understand and test.

# A more complex pipeline: summary report
report = transactions
  .group_by { |t| t[:type] }
  .transform_values { |list| list.sum { |t| t[:amount] } }
# => {:credit=>435_000, :debit=>165_000}

# Zip — combine two arrays element by element
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]

names.zip(scores)
# => [["Alice", 85], ["Bob", 92], ["Charlie", 78]]

names.zip(scores).map { |n, s| { name: n, score: s } }
# => [{:name=>"Alice", :score=>85}, ...]

# each_with_object as a reduce alternative for building Hashes
names.each_with_object({}).with_index do |(n, hash), i|
  hash[n] = scores[i]
end
# => {"Alice"=>85, "Bob"=>92, "Charlie"=>78}
flowchart LR
    A["Raw array\n[transactions]"] --> B["select\n(filter type & amount)"]
    B --> C["sort_by\n(sort descending)"]
    C --> D["map\n(fetch names)"]
    D --> E["[Diana, Alice, Frank]"]

    style A fill:#374151,color:#fff
    style E fill:#374151,color:#fff

Lazy Enumeration #

All the Enumerable methods discussed so far are eager — they process the entire collection before returning a result. This can be a problem when the collection is very large or even infinite.

lazy turns an enumerator into a lazy enumerator — transformations and filters only execute when the results are actually needed (on-demand).

# An infinite range — can't be processed eagerly
# This would hang forever:
# (1..Float::INFINITY).select { |n| n.odd? }.first(5)

# CORRECT: use lazy
(1..Float::INFINITY).lazy.select { |n| n.odd? }.first(5)
# => [1, 3, 5, 7, 9]

# Lazy pipeline — only processes elements as needed
(1..Float::INFINITY)
  .lazy
  .select { |n| n % 3 == 0 }
  .map { |n| n ** 2 }
  .first(5)
# => [9, 36, 81, 144, 225]
# Only processes numbers up to 15 — not millions of numbers!
# Eager vs lazy comparison for large collections
require "benchmark"

data = (1..1_000_000).to_a

Benchmark.bm do |x|
  # Eager: processes all 1 million elements, then takes 10
  x.report("eager:") do
    data.select { |n| n.even? }.map { |n| n * 2 }.first(10)
  end

  # Lazy: only processes until 10 qualifying elements are found
  x.report("lazy: ") do
    data.lazy.select { |n| n.even? }.map { |n| n * 2 }.first(10)
  end
end
# eager:   0.087 seconds
# lazy:    0.000 seconds  (far faster!)
# Practical example: processing a large file line by line without loading everything into memory
File.foreach("large_log.txt")
  .lazy
  .select { |line| line.include?("ERROR") }
  .map { |line| line.strip }
  .first(100)
# Only reads the file until 100 ERROR lines are found

# Infinite Fibonacci with lazy
fibonacci = Enumerator.new do |y|
  a, b = 0, 1
  loop do
    y << a
    a, b = b, a + b
  end
end

fibonacci.lazy.select { |n| n.even? }.first(10)
# => [0, 2, 8, 34, 144, 610, 2584, 10946, 46368, 196418]

lazy changes the return type from Array to Enumerator::Lazy. You need to call .to_a or .force (or .first(n)) to get the result as a regular Array. Use lazy when:

  • The collection is very large (>100k elements) and you only need part of the results
  • Working with infinite sequences or data streams
  • Your pipeline has many filter steps that could cut many elements early

Enumerable vs Array: When to Use What #

Some methods are available on Array but not Enumerable, and vice versa. Understanding the differences helps you write the right code.

MethodEnumerableArrayNotes
map / collectIdentical
select / filterIdentical
find / detectIdentical
sortArray has sort! (in-place)
flattenArray only
push / <<Array only (mutation)
unshiftArray only (mutation)
zipEnumerable returns Array
each_consEnumerable only
each_sliceEnumerable only
chunkEnumerable only
tallyRuby 2.7+
filter_mapRuby 2.7+
lazyEnumerable only
# Methods on Enumerable that return an Enumerator
# when called without a block — useful for advanced chaining

[1, 2, 3].each_with_object([])
# => #<Enumerator: ...>  -- without a block, returns an Enumerator

[1, 2, 3].each_with_object([]).with_index do |(n, arr), i|
  arr << "#{i}:#{n}"
end
# => ["0:1", "1:2", "2:3"]

# Enum::Chain — combining several enumerators (Ruby 2.6+)
combination = [1, 2, 3].each + [4, 5, 6].each
combination.to_a
# => [1, 2, 3, 4, 5, 6]

# Or with the chain method
[1, 2, 3].chain([4, 5, 6], [7, 8, 9]).to_a
# => [1, 2, 3, 4, 5, 6, 7, 8, 9]

Making Your Own Enumerable Class #

Adding Enumerable to your own classes is one of the most elegant patterns in Ruby. You only need one method — each — and you get dozens of methods for free.

class Temperature
  include Comparable   # bonus: can be sorted and compared
  include Enumerable

  attr_reader :celsius

  def initialize(celsius)
    @celsius = celsius
  end

  def fahrenheit
    @celsius * 9.0 / 5 + 32
  end

  def kelvin
    @celsius + 273.15
  end

  def <=>(other)
    @celsius <=> other.celsius
  end

  def to_s
    "#{@celsius}°C"
  end
end

class TemperatureRange
  include Enumerable

  def initialize(from, to, step = 1)
    @from = from
    @to = to
    @step = step
  end

  def each
    temp = @from
    while temp <= @to
      yield Temperature.new(temp)
      temp += @step
    end
  end
end

# After implementing each, all Enumerable methods are available!
this_week = TemperatureRange.new(22, 32, 2)

this_week.map(&:fahrenheit)
# => [71.6, 75.2, 78.8, 82.4, 86.0, 89.6]

this_week.select { |t| t.celsius > 27 }.map(&:to_s)
# => ["28°C", "30°C", "32°C"]

this_week.min
# => 22°C

this_week.max
# => 32°C

this_week.sort.reverse.first(3).map(&:to_s)
# => ["32°C", "30°C", "28°C"]
flowchart TD
    A[Custom Class] --> B{include Enumerable}
    B --> C[Implement each]
    C --> D[Enumerable automatically provides]
    D --> E[map, flat_map]
    D --> F[select, reject, filter_map]
    D --> G[find, any?, all?, none?]
    D --> H[sort, sort_by, min, max]
    D --> I[group_by, tally, chunk]
    D --> J[reduce, sum, count]
    D --> K[lazy, each_cons, each_slice]

    B2[include Comparable] --> L[Implement<br>spaceship operator <=>]
    L --> M[Comparable provides]
    M --> N[< > <= >= between?]
    M --> O[clamp]

Summary #

  • Enumerable works through each — implement this one method in your class, and you get dozens of iteration, transformation, search, sorting, and aggregation methods for free.
  • map for transformation, select/reject for filtering — neither mutates the original collection. Use the bang versions (map!, select!) when you truly want in-place mutation.
  • filter_map combines filtering and transformation — more efficient than select + map and automatically removes nil from the results.
  • reduce is the universal aggregator — use it for cases without a dedicated method; for summation use sum; for extremes use min/max.
  • sort_by is preferred over sort with a block — cleaner and more efficient because it uses the internal Schwartzian Transform.
  • group_by for global grouping, chunk for consecutive grouping — fundamentally different: group_by groups across the whole collection, chunk only groups adjacent elements.
  • Use lazy for large collections or infinite sequences — eager enumeration processes all elements first; lazy enumeration only processes what’s needed and can drastically save memory and time.
  • Chaining is the recommended pattern — chain select, map, sort_by, and other methods into one declarative pipeline instead of imperative loops with state mutation.
  • tally and filter_map are available since Ruby 2.7 — make sure your project’s Ruby version supports them before using in codebases needing backward compatibility.

← Previous: Math   Next: Set →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact