rubyguides

Array#each_with_object

each_with_object(memo) { |element, memo| ... } → memo

Array#each_with_object walks an array one element at a time and hands you a mutable object alongside each element. Mutate that object inside the block and the same object comes back when the loop finishes. The block’s return value is ignored entirely.

Strictly speaking, the method is defined on Enumerable and just inherited by Array. This page covers it as it behaves on an Array receiver, which is where it gets used most often. For the module-level view, see Enumerable#each_with_object.

Syntax

arr.each_with_object(memo) { |element, memo| ... } # => memo
arr.each_with_object(memo)                          # => Enumerator (no block given)

The block receives two arguments: the current element first, the memo second. That order is the opposite of inject and reduce, and getting it wrong is the single most common bug with this method.

Parameters

ParameterTypeRequiredDescription
memoObjectYesThe accumulator. Returned unchanged at the end. Usually [], {}, or a Hash.new with a default block for auto-vivification.
Block{ |element, memo| ... }Yes (in practice)Runs once per element. Must mutate memo in place — the return value is discarded.

What comes back

The exact object you passed in. For an empty array the block never runs and the memo is returned untouched, which is one place each_with_object is safer than inject (no nil surprise).

Building a hash from a list

The common pattern: start with an empty hash and assign keys inside the block.

words = ["apple", "banana", "cherry"]
lengths = words.each_with_object({}) { |word, hash| hash[word] = word.length }
lengths
# => {"apple"=>5, "banana"=>6, "cherry"=>6}

Frequency tally without tally

Hash.new(0) makes += safe on a missing key, so you can count occurrences in one pass. The default value of 0 is what lets tally[score] += 1 work even on a key the hash has never seen before, and it also sets the starting count for brand-new buckets.

scores = [10, 20, 10, 30, 20, 10, 40]
counts = scores.each_with_object(Hash.new(0)) { |score, tally| tally[score] += 1 }
counts
# => {10=>3, 20=>2, 30=>1, 40=>1}

If you only need equality counts, Array#tally does this in one method call. Reach for each_with_object when the counts need extra work (weighting, bucketing, combining with other data).

Grouping with an auto-vivifying hash

Hash.new { |h, k| h[k] = [] } creates the bucket on first read, so you can group without checking has_key? first.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
grouped = numbers.each_with_object(Hash.new { |h, k| h[k] = [] }) do |n, hash|
  n.even? ? hash[:even] << n : hash[:odd] << n
end
grouped
# => {:even=>[2, 4, 6, 8, 10], :odd=>[1, 3, 5, 7, 9]}

Building a transformed array

This is the simplest use of the method: an array goes in, an array comes out, with each element transformed. Start with [], push into it, and return the result. It is a one-line pattern, but the block still has to mutate the memo, which is why arr << n * 2 works and a bare n * 2 would silently drop every value.

numbers = [1, 2, 3, 4, 5]
doubled = numbers.each_with_object([]) { |n, arr| arr << n * 2 }
doubled
# => [2, 4, 6, 8, 10]

This is fine, but for “one transformed value per input” cases, Array#map reads more directly. The two-line version with map is what most Ruby developers reach for first, and you should default to it whenever the output has the same shape as the input.

doubled = numbers.map { |n| n * 2 }
# => [2, 4, 6, 8, 10]

Use each_with_object for the array case when the block also needs to feed something else (an index lookup, a parallel hash, a counter) and you want both in a single pass.

each_with_object vs inject / reduce

They iterate the same way and pass a running value alongside each element, so they get mixed up. The differences:

inject / reduceeach_with_object
Block argument order|memo, element||element, memo| (reversed)
Block return valueBecomes the next memoDiscarded — ignored entirely
How the memo advancesReturn a new valueMutate the memo in place
Final returnThe last block return valueThe exact object passed in
Empty arrayReturns nil (or raises)Returns the memo untouched

Reach for inject when you are computing a scalar (a sum, a max, a concatenated string). Reach for each_with_object when you are filling a mutable collection.

nums = [1, 2, 3, 4]

# inject — return value drives the next accumulator
nums.inject(0) { |sum, n| sum + n }
# => 10

# each_with_object — mutate the memo
nums.each_with_object([]) { |n, arr| arr << n * 2 }
# => [2, 4, 6, 8]

Gotchas

The block return value is thrown away. Returning a new object from the block does nothing. The memo is only updated when you mutate it with <<, []=, +=, concat, or any other in-place method.

The argument order is reversed from inject. Copy-paste between the two and the variables swap silently. The code runs, but it does the wrong thing.

No block returns an Enumerator. Useful for chaining, but rarely the reason you reach for this method:

[1, 2, 3].each_with_object([])
# => #<Enumerator: ...>

See Also