Enumerable#inject
enum.inject(initial) { |acc, elem| } The inject method (also known as reduce or fold in other programming languages) is one of the most powerful methods in Ruby Enumerable module. It 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 inject accumulates all elements using a starting value:
numbers = [1, 2, 3, 4, 5]
sum = numbers.inject(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. Picture a running tally that updates on each pass: start with 0, fold in 1 to get 1, then 2 to get 3, 3 to get 6, and finally 4 and 5 to reach 15. The accumulator carries the state forward while each element contributes its piece.
Shorthand symbol syntax
Ruby provides a convenient shorthand when the operation is a simple method call:
numbers = [1, 2, 3, 4, 5]
sum = numbers.inject(:+)
# => 15
This is equivalent to the block version but more concise. You can use any binary method name as a symbol. Internally, Ruby dispatches send(:+, acc, num) for every element, which means inject(:*) multiplies, inject(:concat) joins strings, and inject(:merge) unifies hashes. The symbol must name a method that accepts one argument and produces a value compatible with the accumulator.
Without initial value
If you omit the initial value, inject uses the first element as the starting accumulator:
numbers = [1, 2, 3, 4, 5]
sum = numbers.inject { |acc, num| acc + num }
# => 15
This works because Ruby starts with element 1 as the accumulator and then processes elements 2 through 5. On the first iteration, acc is already 1 and num is 2, so the block runs starting from the second element. The fold proceeds naturally from there, and the result is identical to the seeded version. The only difference is conceptual: without a seed, the first element serves as the starting point rather than joining the accumulation as a participant.
Caution: With an empty collection and no initial value, inject returns nil:
[].inject { |acc, x| acc + x }
# => nil
An empty collection with no seed gives inject nothing to work with — there is no first element to become the accumulator and no fallback value. The nil result signals this edge case to the caller. When the downstream code cannot handle nil, always provide an explicit initial value so inject returns a sensible default for empty input.
Common Examples
Summing Numbers
prices = [10, 20, 30, 40]
total = prices.inject(0, :+)
# => 100
The two-argument form of inject passes both the initial value and the operation symbol in a single call. This is the most compact notation for a simple accumulation when the operation is already known as a method name. It reads as “start at zero and add everything,” and it handles the empty-collection edge case gracefully by returning the seed value 0.
Finding maximum value
scores = [45, 87, 23, 92, 55]
highest = scores.inject { |max, score| score > max ? score : max }
# => 92
This fold selects rather than combines. The block compares the current score against the running maximum; the larger value becomes the accumulator. With no initial value, the first element 45 seeds the comparison and is immediately replaced by 87. After processing the full list, 92 remains as the final accumulator because no subsequent score beats it.
Building a String
words = ["Hello", "World", "from", "Ruby"]
sentence = words.inject("") { |str, word| str + word + " " }
# => "Hello World from Ruby "
String building via inject starts with an empty string seed and appends each word followed by a space. The trailing space is a minor imperfection — it appears because every word, including the last, gets a space appended. In production code words.join(" ") is the idiomatic Ruby choice, but the inject version shows how accumulation generalises beyond arithmetic to any type that supports a binary operation.
Flattening nested arrays
matrix = [[1, 2], [3, 4], [5, 6]]
flat = matrix.inject([]) { |acc, arr| acc + arr }
# => [1, 2, 3, 4, 5, 6]
Flattening via inject builds a one-dimensional array by concatenating each sub-array onto the accumulator. Starting with [], each inner array is joined with Array#+. The operation mirrors what flatten does internally, but inject exposes the reduction logic explicitly. In idiomatic Ruby, flat_map(&:itself) or simply flatten is clearer, though the inject form remains a useful teaching tool for understanding how accumulators can change type during the fold.
Creating a hash from arrays
keys = [:a, :b, :c]
values = [1, 2, 3]
hash = keys.zip(values).inject({}) { |h, (k, v)| h.merge(k => v) }
# => { a: 1, b: 2, c: 3 }
The zip call pairs keys and values by position: [:a, 1], [:b, 2], [:c, 3]. Each pair is destructured in the block as (k, v) and merged into the accumulator. Because merge returns a new hash each time, this pattern creates intermediate hashes at every step. For performance-sensitive code with many entries, each_with_object({}) mutates a single hash in place and avoids the allocation overhead. The functional purity of inject comes at the cost of extra object creation, which is a reasonable tradeoff for small to medium-sized datasets. In most everyday Ruby code, either approach works fine, and the choice is guided by whether the surrounding code favours functional or imperative style.
The reduce Alias
reduce is an alias for inject — 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” is borrowed from functional programming, where a reducer function collapses a collection into a single accumulated value. Both names dispatch to the same C implementation, so performance and behaviour are identical regardless of which spelling you use. Pick the one that aligns with your team’s conventions and stick with it for consistency.
Practical Patterns
Factorial Calculation
def factorial(n)
(1..n).inject(1, :*)
end
factorial(5)
# => 120
Factorial via inject multiplies every integer in the range (1..n), seeded with 1 as the multiplicative identity. For n = 5, the computation is 1 * 1 * 2 * 3 * 4 * 5 = 120. The seed handles factorial(0) correctly: an empty range times 1 returns 1, which matches the mathematical definition. This is a clean, functional expression of a classic mathematical operation.
Chaining Transformations
result = [1, 2, 3, 4, 5]
.inject([]) { |acc, x| acc << x * 2 }
.inject(0, :+)
# => 30
This chain doubles each number and sums the results in two separate inject calls. The first builds an intermediate array [2, 4, 6, 8, 10] via accumulation, and the second folds that array into 30. In practice, [1, 2, 3, 4, 5].sum { |x| x * 2 } achieves the same result more directly, but the chained inject form illustrates how accumulators can pass structured data between pipeline stages.
Performance Notes
inject 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 inject
[1, 2, 3].inject(0, :+)
# Using sum (available in Ruby 2.4+)
[1, 2, 3].sum
For numeric summation, sum is the preferred choice in modern Ruby because it is both more readable and often faster — it uses specialised C-level addition rather than the general-purpose block dispatch of inject. Reserve inject for cases where the accumulation logic is more complex than a single method call, such as building a hash, finding a maximum, or chaining multiple transformations. When you do reach for inject, the symbol shorthand inject(:+) offers a middle ground between the brevity of sum and the full flexibility of a custom block.
Return value
inject returns the final accumulated value. For empty collections:
- With initial value: returns that initial value
- Without initial value: returns
nil
Providing an initial value is a defensive habit that prevents nil from propagating through downstream code. When nil arrives where a number or string was expected, the resulting NoMethodError can be cryptic and hard to trace. An explicit seed documents the intended type of the result and guarantees a sensible fallback for empty input.
Edge Cases
# Single element with initial value
[5].inject(10, :+)
# => 15
# Single element without initial value
[5].inject { |acc, x| acc + x }
# => 5
# With strings (using concatenation)
["a", "b", "c"].inject(:+)
# => "abc"
See Also
Array#sum— simpler for numeric sumsArray#select— filter elements to a new arrayEnumerable#each_with_index— iterate with index positionsEnumerable#partition— split into two arrays by condition