← Reference · Nestor G Pestelos Jr · Print this page

Programming Languages · Ruby

Filter, Group, and Reduce (Ruby Enumerable)

Reference entry · last updated September 19, 2026

Filter, group, and reduce are three iteration protocols in Ruby's Enumerable module. Filter keeps or drops elements by a predicate, group partitions elements into buckets, and reduce collapses elements into a single value.[1] Each protocol is a distinct way of threading state from one element to the next, and each is available on every class that includes the module.

First principles and definitions

Enumerable is a module, not a class. A collection includes the module and defines one required method, each, which yields successive elements to a block. The module's documentation states that virtually all of its methods call #each in the including class, so the protocol gives dozens of methods over a single iteration primitive.[1]

The three protocols differ in the state they thread across elements:

Reduce is the general case. It is the fold that the other two specialize. Filter and group can each be written as a reduce that threads an array or a hash, but a reduce cannot be written as a filter or a group without keeping the full collection. The three names are the standard vocabulary: filter is also called select, group is often called partition into buckets, and reduce is also called inject or fold.[1]

Filter

select {|element| ... }  →  array
select                   →  enumerator

select calls the block with successive elements and returns an array of those for which the block returns a truthy value. find_all is the original name, and filter is an alias of it.[1]

(0..9).select { |i| i % 3 == 0 }   # => [0, 3, 6, 9]
(0..9).filter { |i| i % 3 == 0 }   # => [0, 3, 6, 9]

reject is the complement. It returns the elements for which the block returns nil or false.[1]

(0..9).reject { |i| i % 3 == 0 }   # => [1, 2, 4, 5, 7, 8]

On a Hash, select yields each entry as a key and a value, and returns a Hash rather than an array.[1]

{foo: 0, bar: 1, baz: 2}.select { |key, value| key.start_with?('b') }
# => {:bar=>1, :baz=>2}

With no block, select and reject each return an Enumerator instead of evaluating immediately.[1]

Group

group_by {|element| ... }  →  hash
group_by                   →  enumerator

group_by returns a hash in which each key is a block return value and each value is an array of the elements for which the block returned that key.[1] Keys appear in the order their first element appeared, consistent with Hash insertion order.

(1..6).group_by { |i| i % 3 }
# => {1=>[1, 4], 2=>[2, 5], 0=>[3, 6]}

On a Hash, the block receives a key and a value, so the buckets contain [key, value] pairs.[1]

{foo: 0, bar: 1, baz: 0, bat: 1}.group_by { |key, value| value }
# => {0=>[[:foo, 0], [:baz, 0]], 1=>[[:bar, 1], [:bat, 1]]}

With no block, it returns an Enumerator.[1]

Reduce

inject(symbol)                   →  object
inject(initial_operand, symbol)  →  object
inject {|memo, operand| ... }    →  object
inject(initial_operand) {|memo, operand| ... }  →  object

reduce is an alias of inject. It combines the operands either with a method named by a symbol or with a block that receives the accumulator and the next operand.[1]

(1..4).inject(:+)                       # => 10
(1..4).inject(10, :+)                   # => 20
(1..4).inject { |sum, n| sum + n*n }    # => 30
(1..4).inject(2) { |sum, n| sum + n*n } # => 32

The initial operand changes the operands. Without it, the operands are the elements themselves, so the first element starts the accumulator. With it, that value is the first operand and the elements follow. On an empty collection, reduce without an initial operand returns nil, because there is no first operand to start from, while a supplied initial operand is returned.[1]

[].reduce(:+)      # => nil
[].reduce(0, :+)   # => 0

sum

sum is the common special case of reduce for addition. Its initial value defaults to 0, and an optional block transforms each element before adding. The documentation notes that for ranges the result may be computed with Gauss's summation formula rather than element by element.[1]

(1..100).sum                  # => 5050
(1..4).sum { |i| i*i }        # => 30
[].sum                        # => 0

Composition

The three protocols chain naturally, because each returns an object that responds to each or supplies the next stage's input. A filter followed by a group and a reduce is the standard pipeline for "keep these, bucket them, then total each bucket."

rows
  .select  { |row| row[:qty] > 0 }
  .group_by { |row| row[:category] }
  .transform_values { |group| group.sum { |row| row[:qty] } }

Use filter when the result is a subset, group when the result is a set of buckets, and reduce or sum when the result is one value. A filter that also needs to total is two operations in sequence, not one method that does both.

Cost

Each protocol makes one pass over the collection, so each is linear in the number of elements. The difference is the space they thread. Filter allocates a result collection. Group allocates a hash plus one array per key. Reduce holds a single accumulator, and sum may avoid per-element addition on ranges through the documented Gauss formula.[1]

The linear bounds describe the expected behavior of one pass over n elements and the result containers each protocol builds. They are conventional costs, not asymptotic guarantees published in the Ruby documentation.

See also

References

  1. ^ Ruby Documentation, "Module Enumerable," Ruby 3.3.0. Free full text: docs.ruby-lang.org/en/3.3/Enumerable.html