Enumerable Deep Dive
Enumerable is one of Ruby's richest modules, offering dozens of methods beyond the well-known each, map, and select. Mastering the fuller toolkit — sort_by, group_by, partition, each_with_object, tally, flat_map, zip, and lazy enumerators — lets you express complex data transformations in a single expressive chain instead of nested loops and mutable accumulator variables. All of these methods are available to any class that mixes in Enumerable and defines each, as well as to Array, Hash, Range, and Set out of the box.
Cricket analogy: Beyond just watching every ball (each) or scoring runs per over (map), a stats analyst reaches for sort_by to rank batsmen, group_by to bucket by team, and partition to split centuries from non-centuries in one expressive chain.
sort_by, group_by, and partition
sort_by sorts using a computed key rather than the elements' natural comparison, and is generally faster than sort with a block because it computes the key once per element (a Schwartzian transform) rather than recomputing it on every comparison. group_by buckets elements into a Hash keyed by the block's return value. partition splits a collection into exactly two arrays — elements where the block is truthy, and where it's falsy.
Cricket analogy: Ranking bowlers by economy rate computed once per bowler (sort_by) is faster than recomputing it on every comparison; group_by buckets batsmen into a Hash by their team, and partition splits the innings into boundary-scoring shots versus dot balls.
employees = [
{ name: 'Ada', dept: 'Engineering', salary: 95_000 },
{ name: 'Grace', dept: 'Engineering', salary: 110_000 },
{ name: 'Alan', dept: 'Sales', salary: 70_000 }
]
by_salary = employees.sort_by { |e| -e[:salary] }
by_dept = employees.group_by { |e| e[:dept] }
#=> { 'Engineering' => [Ada, Grace], 'Sales' => [Alan] }
high_earners, others = employees.partition { |e| e[:salary] > 90_000 }each_with_object and tally
each_with_object is a cleaner alternative to reduce when the accumulator is a mutable object (like a Hash or Array) — it yields the accumulator alongside each element and always returns the accumulator automatically, eliminating the need to explicitly return it. tally counts occurrences of each unique element, returning a Hash of element to count — a shortcut that replaces a common each_with_object/reduce pattern.
Cricket analogy: Building a Hash of runs-per-batsman while iterating deliveries is cleaner with each_with_object than reduce, since it yields the accumulator alongside each ball; tally then counts how many times each bowler dismissed a batsman in one shortcut.
words = %w[apple banana apple cherry banana apple]
counts = words.each_with_object(Hash.new(0)) { |word, h| h[word] += 1 }
#=> {'apple'=>3, 'banana'=>2, 'cherry'=>1}
words.tally
#=> {'apple'=>3, 'banana'=>2, 'cherry'=>1} (equivalent, built in since Ruby 2.7)Lazy enumerators for large or infinite sequences
Chaining several Enumerable methods on a large collection builds an intermediate array at every step, which is wasteful. .lazy returns a lazy enumerator that defers evaluation, processing elements one at a time through the whole chain only as they're requested — essential for working with infinite sequences (like 1..Float::INFINITY) or avoiding unnecessary work when only a few results are needed.
Cricket analogy: Chaining filters and transforms over every ball ever bowled in Test history would build a huge intermediate array at each step; .lazy defers evaluation, useful when you only need the first five centuries matching a condition rather than scanning everything.
first_five_even_squares = (1..Float::INFINITY).lazy
.map { |n| n ** 2 }
.select(&:even?)
.first(5)
#=> [4, 16, 36, 64, 100]flat_map combines map and flatten(1) in one pass — useful when your block itself returns an array per element and you want a single flattened result, such as mapping each sentence in an array to its array of words and getting back one flat array of all words.
sort_by { |e| -e[:salary] } works for numeric descending sort, but for non-numeric keys, use sort_by { ... }.reverse or the two-argument sort { |a, b| b <=> a } instead, since you can't negate a String or arbitrary object directly.
sort_bysorts by a computed key, generally faster thansortwith a block for expensive comparisons.group_bybuckets elements into a Hash keyed by the block's result.partitionsplits a collection into two arrays based on a truthy/falsy block.each_with_objectis cleaner thanreducefor building up a mutable accumulator.tallycounts occurrences of unique elements without any block needed..lazydefers evaluation through a chain, essential for infinite or very large sequences.
Practice what you learned
1. What does `group_by` return?
2. What does `partition` return?
3. Why is each_with_object often preferred over reduce for building a Hash?
4. Why use `.lazy` before chaining map/select on `(1..Float::INFINITY)`?
5. What does `tally` do?
Was this page helpful?
You May Also Like
each, map, and select
Master the three most commonly used Enumerable methods — each for side effects, map for transformation, and select for filtering — and when to reach for which.
reduce and inject
Understand Ruby's reduce/inject method — how it folds a collection into a single accumulated value, and how to use it for sums, grouping, and building complex results.
Comparable and Enumerable Modules
See how Ruby's Comparable and Enumerable modules let a class gain dozens of methods for free by implementing just one core method each: `<=>` or `each`.
Custom Iterators
Learn how to build your own iterator methods with yield and Enumerator, giving your classes the same each/map/select power that Ruby's built-in collections enjoy.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics