Enumerable#reduce
enum.reduce(initial) { |acc, elem| } The reduce method (also known as inject or fold in other programming languages) accumulates values by applying a binary operation to each element, carrying the result forward until the entire collection is reduced to a single value.
This method is essential for tasks like summing numbers, building strings, finding maximum values, or any operation that combines all elements into one result.
Basic Usage
The simplest form of reduce accumulates all elements using a starting value:
numbers = [1, 2, 3, 4, 5]
sum = numbers.reduce(0) { |acc, num| acc + num }
# => 15
The block receives two parameters: the accumulator and the current element. The return value of the block becomes the new accumulator for the next iteration. The mental model is a running total that gets updated on each pass: start with 0, add 1 to get 1, add 2 to get 3, add 3 to get 6, and so on. After processing all five numbers, the accumulator holds 15, which reduce returns.
Shorthand symbol syntax
Ruby provides a convenient shorthand when the operation is a simple method call:
numbers = [1, 2, 3, 4, 5]
sum = numbers.reduce(:+)
# => 15
This is equivalent to the block version but more concise. You can use any binary method name as a symbol. Ruby internally calls send(:+, acc, num) for each element, so reduce(:*) multiplies, reduce(:concat) joins strings, and reduce(:merge) combines hashes. Any method that takes one argument and returns a value of a compatible type can be used this way.
Without initial value
If you omit the initial value, reduce uses the first element as the starting accumulator:
numbers = [1, 2, 3, 4, 5]
sum = numbers.reduce { |acc, num| acc + num }
# => 15
Ruby starts with element 1 as the accumulator and then processes elements 2 through 5. Internally, the first iteration treats the first element as the accumulator and applies the block starting from the second element. For addition this gives the same result as reduce(0, :+), but the mental model is slightly different: you are folding the list from left to right without an explicit starting point.
Caution: With an empty collection and no initial value, reduce returns nil:
[].reduce { |acc, x| acc + x }
# => nil
An empty collection with no seed leaves reduce with nothing to accumulate — there is no first element to serve as the starting accumulator, and no initial value to fall back on. The nil result is a deliberate design choice that lets you detect the empty-input case by checking the return value. When nil would cause problems downstream, always provide an initial value so the method has a sensible default to return.
Common Examples
Summing Numbers
prices = [10, 20, 30, 40]
total = prices.reduce(0, :+)
# => 100
Passing both arguments to reduce — the initial value 0 and the symbol :+ — is the two-argument shorthand. The first argument seeds the accumulator, and the second names the operation. This form reads naturally as “start at zero and add everything,” and it is the most compact way to express a simple accumulation when you already know the binary method name.
Finding maximum value
scores = [45, 87, 23, 92, 55]
highest = scores.reduce { |max, score| score > max ? score : max }
# => 92
The block compares each score against the running maximum. If the current score is larger, it becomes the new accumulator; otherwise, the existing maximum carries forward. This is a fold that does not combine values in the arithmetic sense — it selects. Because no initial value is given, the first element 45 starts as the accumulator and gets compared against 87, which replaces it. The final accumulator after processing all scores is 92.
Building a String
words = ["Hello", "World", "from", "Ruby"]
sentence = words.reduce("") { |str, word| str + word + " " }
# => "Hello World from Ruby "
String concatenation via reduce builds the sentence word by word, appending a space after each. The initial value is an empty string, so the first iteration concatenates "" + "Hello" + " ". Note the trailing space at the end — this is a side effect of appending a space after every word, including the last. For production code, words.join(" ") is cleaner and faster, but the reduce version illustrates how accumulation works with non-numeric types.
Flattening nested arrays
matrix = [[1, 2], [3, 4], [5, 6]]
flat = matrix.reduce([]) { |acc, arr| acc + arr }
# => [1, 2, 3, 4, 5, 6]
This pattern converts a two-dimensional array into a flat array by repeatedly concatenating sub-arrays. Starting with an empty array as the accumulator, each inner array is appended via Array#+. The operation is functionally equivalent to matrix.flatten, but the reduce version is explicit about the flattening strategy. In Ruby, flatten or flat_map should be preferred for readability, though the reduce form is a useful teaching example that shows how accumulation generalises beyond simple arithmetic.
Creating a hash from arrays
keys = [:a, :b, :c]
values = [1, 2, 3]
hash = keys.zip(values).reduce({}) { |h, (k, v)| h.merge(k => v) }
# => { a: 1, b: 2, c: 3 }
The zip method pairs corresponding elements from keys and values into [[:a, 1], [:b, 2], [:c, 3]]. The block destructures each pair into k and v and merges them into the accumulator hash. With each iteration, a new merged hash is returned as the accumulator for the next round. For large datasets, each_with_object({}) is often preferred because it mutates a single hash instead of creating a new one each time, but reduce makes the functional intent clearer. The choice comes down to whether you value immutability during the fold or want to minimise allocations.
The inject Alias
inject is an alias for reduce — they behave identically:
[1, 2, 3].reduce(0, :+)
# => 6
[1, 2, 3].inject(0, :+)
# => 6
Many developers prefer reduce because the name more intuitively describes what happens. The term “reduce” comes from functional programming, where a reducer collapses a collection into a single value. “inject” comes from the Smalltalk heritage and describes the act of injecting an operation between each pair of elements. Ruby supports both names, and the community uses them interchangeably.
Practical Patterns
Factorial Calculation
def factorial(n)
(1..n).reduce(1, :*)
end
factorial(5)
# => 120
Factorial is a classic reduce example: start with 1, multiply by each integer from 1 to n. The range (1..5) produces [1, 2, 3, 4, 5], and reduce(1, :*) computes 1 * 1 * 2 * 3 * 4 * 5 = 120. The initial value of 1 is the multiplicative identity — without it, reduce(:*) on [1, 2, 3, 4, 5] would also produce 120, but the explicit seed makes the intent clear and handles the edge case of factorial(0) correctly by returning 1 from an empty range.
Chaining Transformations
result = [1, 2, 3, 4, 5]
.reduce([]) { |acc, x| acc << x * 2 }
.reduce(0, :+)
# => 30
Chaining two reduce calls illustrates how accumulation can be composed. The first reduce doubles each number and collects the results into an array via acc << x * 2. The second reduce then sums that array. This is more verbose than [1, 2, 3, 4, 5].map { |x| x * 2 }.sum, but it demonstrates that reduce can build intermediate collections — it is not limited to producing a single scalar. In practice, prefer map + sum for this exact case.
Grouping by Attribute
people = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Carol", age: 30 }
]
grouped = people.reduce({}) do |acc, person|
age = person[:age]
acc[age] ||= []
acc[age] << person[:name]
acc
end
# => { 30 => ["Alice", "Carol"], 25 => ["Bob"] }
Grouping by attribute with reduce uses the accumulator as a mutable hash. The ||= operator initialises an empty array the first time a given age is encountered, and subsequent occurrences push additional names onto that array. The block must return acc explicitly — if you forget the final acc line, the return value of acc[age] << person[:name] (which is the array, not the hash) becomes the accumulator for the next iteration, breaking the grouping logic.
Performance Notes
reduce is efficient and lazy in the sense that it does not create intermediate collections when using the symbol shorthand. However, for simple sums, specialized methods like sum may be more readable:
# Using reduce
[1, 2, 3].reduce(0, :+)
# Using sum (available in Ruby 2.4+)
[1, 2, 3].sum
Return value
reduce returns the final accumulated value. For empty collections:
- With initial value: returns that initial value
- Without initial value: returns
nil
When you provide an explicit seed, you get a meaningful default even for empty input. Without a seed, reduce cannot guess what the accumulation type should be, so nil is the only safe fallback. This is why providing an initial value is a defensive habit: it prevents nil from leaking into downstream code where a NoMethodError might be harder to trace.
Edge Cases
# Single element with initial value
[5].reduce(10, :+)
# => 15
# Single element without initial value
[5].reduce { |acc, x| acc + x }
# => 5
# With strings (using concatenation)
["a", "b", "c"].reduce(:+)
# => "abc"
See Also
Enumerable#inject— alias for reduceArray#sum— simpler for numeric sumsEnumerable#each_with_object— iterate while building an objectEnumerable#partition— split into two arrays by condition