reduce and inject

Published by Programming Line in Ruby. Last updated .

reduce (and its exact synonym inject) is the Swiss Army knife of Ruby’s Enumerable module: it walks a collection and folds every element into a single accumulated value. Instead of writing a manual loop with a variable you update by hand, you hand Ruby a starting value and a block that says “combine the running total with the next element,” and reduce does the bookkeeping. It is how you sum numbers, build hashes, find extremes, or flatten any collection into one result — and understanding it well makes the rest of Ruby’s Enumerable methods click into place.

Overview: How reduce and inject Work

reduce and inject are defined once in the Enumerable module and mixed into every class that implements each — Array, Hash, Range, Set, and anything you write yourself that includes Enumerable. Under the hood, reduce is built on top of each: it keeps a running value called the accumulator, calls your block once per element with (accumulator, element), and whatever the block returns becomes the accumulator going into the next call. When the collection is exhausted, the final accumulator value is the return value of reduce itself.

The two names exist for historical and stylistic reasons: inject comes from Smalltalk (“inject this value into the collection”), and reduce matches the term used in most other languages with functional-programming roots (JavaScript, Python, Elixir). They are the same method under two names — use whichever reads better in context. Most modern Ruby style guides lean toward reduce as the more familiar term for readers coming from other languages, but you will see both in real codebases.

Because everything in Ruby is an object, the accumulator can be anything: a number, a string, an array, a hash, or a custom object. This is what makes reduce so much more powerful than a simple sum or count method — it can build up any shape of result, not just a single number.

Syntax

enumerable.reduce(initial_value) { |accumulator, element| ... }
enumerable.reduce(:symbol)
enumerable.reduce(initial_value, :symbol)
enumerable.inject(initial_value) { |accumulator, element| ... }
  • initial_value — the starting value for the accumulator. If omitted, the collection’s first element is used as the initial accumulator and iteration starts from the second element.
  • { |accumulator, element| … } — a block that receives the running accumulator and the current element, and must return the new accumulator.
  • :symbol — a shorthand for calling that method (usually an operator like :+ or :*) on the accumulator, passing the element as its argument, instead of writing a block at all.
  • You can combine both: reduce(initial_value, :symbol) sets a starting value and uses the symbol shorthand.

Common symbols used with the shorthand form:

Symbol Effect Example
:+ Sums elements [1, 2, 3].reduce(:+) → 6
:* Multiplies elements [1, 2, 3].reduce(:*) → 6
:merge Merges hashes [{a: 1}, {b: 2}].reduce(:merge) → a combined hash
:concat Concatenates arrays [[1], [2, 3]].reduce(:concat) → a flattened array

Examples

Example 1: Summing numbers with an initial value.

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

sum = numbers.reduce(0) { |total, n| total + n }

puts sum

Output:

15

Here the accumulator starts at 0. On each pass, the block adds the current element to total and returns the new sum, which becomes total for the next element. After the last element, reduce returns the final total.

Example 2: The symbol shorthand.

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

sum_by_symbol = numbers.reduce(:+)
product = numbers.reduce(:*)

puts sum_by_symbol
puts product

Output:

15
120

With no initial value, the first element (1) becomes the starting accumulator, and reduce calls 1 + 2, then 3 + 3, and so on, exactly as if you had written total.send(:+, n) for every remaining element. The symbol form is concise for simple binary operations, but it only works when the operator method takes exactly one argument, which is why it cannot replace every block-based reduction.

Example 3: Building a hash (tallying word frequency).

words = %w[apple banana apple cherry banana apple]

tally = words.reduce(Hash.new(0)) do |counts, word|
  counts[word] += 1
  counts
end

puts tally.inspect

Output:

{"apple"=>3, "banana"=>2, "cherry"=>1}

The initial value here is a Hash.new(0), a hash whose default value is 0 for any missing key. Each pass increments the count for the current word and, crucially, the block’s last line explicitly returns counts so it becomes the accumulator for the next iteration. This pattern — accumulate into a mutable object and return that same object — is extremely common for building hashes and arrays with reduce.

Example 4: Finding the longest word with no initial value.

words = ["ruby", "python", "go", "javascript"]

longest = words.reduce do |current_longest, word|
  word.length > current_longest.length ? word : current_longest
end

puts longest

Output:

javascript

Without an initial value, "ruby" becomes the starting accumulator and the block only runs for "python", "go", and "javascript". Each call compares the current candidate’s length against the running longest word and keeps whichever is longer.

How it works step by step

Take Example 1, numbers.reduce(0) { |total, n| total + n } over [1, 2, 3, 4, 5]. Internally, something equivalent to this happens:

  • Start with total = 0 (the initial value you supplied).
  • Call the block with (0, 1) → returns 1. Now total = 1.
  • Call the block with (1, 2) → returns 3. Now total = 3.
  • Call the block with (3, 3) → returns 6. Now total = 6.
  • Call the block with (6, 4) → returns 10. Now total = 10.
  • Call the block with (10, 5) → returns 15. Now total = 15.
  • No elements remain, so reduce returns the final accumulator: 15.

When you omit the initial value, Ruby simply skips the first “seed” step: it takes the first element as the starting accumulator and begins calling the block from the second element onward. If the collection has exactly one element and no initial value is given, the block never runs at all and that single element is returned directly. If the collection is empty and no initial value is given, there is nothing to seed the accumulator with, so reduce returns nil.

Common Mistakes

Mistake 1: Forgetting to return the accumulator from the block. When the accumulator is a mutable object like a hash, it is tempting to write the mutation as the last line and assume it “just works”:

words = %w[apple banana apple]

tally = words.reduce(Hash.new(0)) do |counts, word|
  counts[word] += 1
end

puts tally.inspect

This looks reasonable, but counts[word] += 1 evaluates to the new integer count, not the hash. That integer becomes the accumulator for the next call, so on the second iteration Ruby tries to call []= on an Integer and raises NoMethodError. The fix is to make sure the block’s final expression is the accumulator itself, as in Example 3 above, where counts is explicitly the last line.

Mistake 2: Assuming reduce on an empty collection returns a sensible default.

totals = []

result = totals.reduce(:+)

puts result.inspect

Output:

nil

With no initial value and no elements, there is nothing to seed the accumulator, so the result is nil rather than 0. Since nil is one of only two falsy values in Ruby (the other being false), code like if result would silently treat this as “no result” even though you may have expected a numeric zero. Always pass an explicit initial value (reduce(0, :+)) when the collection might be empty and you need a guaranteed type back.

Mistake 3: Misjudging the order of operations for non-commutative operators.

numbers = [10, 3, 2]

result = numbers.reduce(:-)

puts result

Output:

5

It is easy to expect some other grouping, but reduce always folds strictly left to right: it starts with 10, computes 10 - 3 to get 7, then 7 - 2 to get 5. For commutative operators like :+ and :* the order never matters, but for subtraction, division, or custom operators it does — always trace through the left-to-right order by hand when the operator is not commutative.

Best Practices

  • Always supply an explicit initial value when the collection could be empty, so you get a predictable type back instead of an unexpected nil.
  • When accumulating into a mutable object (hash, array, custom object), make sure the last line of the block returns that object.
  • Prefer the symbol shorthand (reduce(:+)) for simple one-argument operators; it is shorter and communicates intent immediately.
  • If you are only accumulating into a mutable object and never care about reduce‘s own return value semantics, consider each_with_object instead — it always returns the object you started with, which avoids Mistake 1 entirely.
  • Use reduce for genuine folding (turning many values into one summary value); use map/select/each when you are transforming or filtering rather than combining.
  • Name the block parameters meaningfully (total, counts, longest) instead of generic names like acc and x — it makes the fold’s intent obvious at a glance.
  • Remember reduce and inject are interchangeable; pick one convention for your codebase and stay consistent.

Practice Exercises

  • Given words = ["a", "bb", "ccc", "dddd"], use reduce to compute the total number of characters across all words. Expected output: 10.
  • Given pairs = [[:a, 1], [:b, 2], [:c, 3]], use reduce with an initial empty hash to build {a: 1, b: 2, c: 3}. Hint: the block receives an array like [:a, 1] as the element each time.
  • Given numbers = [4, 1, 7, 3, 9, 2], use reduce without a block (symbol shorthand) to find the maximum value. Hint: there is a comparison operator symbol you can pass directly, or you can write a block that compares two values with a ternary.

Summary

  • reduce and inject are the same method, defined in Enumerable, that folds a collection into a single accumulated value.
  • The block receives (accumulator, element) and must return the new accumulator; whatever it returns is what the next call sees.
  • An initial value seeds the accumulator; without one, the first element is used as the seed and iteration starts from the second element.
  • The symbol shorthand (reduce(:+), reduce(0, :+)) is a concise alternative to a block for simple one-argument operators.
  • Forgetting to return the accumulator as the block’s last line is the most common bug when building hashes or arrays with reduce.
  • reduce on an empty collection with no initial value returns nil, which is falsy — always guard for this when the collection size is not guaranteed.