Hash#each_with_object
Overview
each_with_object iterates over a hash, yielding each key-value pair and an accumulator object to the block. Unlike each, which returns the original hash, each_with_object returns whatever object you pass in — making it useful for transforming a hash into a different data structure.
The method is actually defined on Enumerable, so it works on any enumerable, including hashes. When iterating over a hash, the block receives key-value pairs as a two-element array as the first argument.
That accumulator pattern is a good fit when you want to build a new array or hash while reading the original data once. The block stays focused on the transformation, and the object you are building is visible from the first line of the call.
Signature
each_with_object(obj) { |(key, value), memo| block } -> obj
each_with_object(obj) -> Enumerator
Parameters
| Parameter | Description |
|---|---|
obj | The initial value of the accumulator — any object. Returned after iteration completes. |
Return Value
Returns the passed obj after processing all entries. The original collection is unchanged. The basic examples below show how data can be transformed into an array, another hash, or a running count, all using the same each_with_object pattern.
Basic Usage
Building an array from a hash
scores = { alice: 95, bob: 82, carol: 78 }
result = scores.each_with_object([]) do |(name, score), arr|
arr << "#{name}: #{score}"
end
# => ["alice: 95", "bob: 82", "carol: 78"]
This form is handy when the end result is a flat list of labels, rows, or summary strings. The code reads as “take each pair and place a formatted version into the array,” which keeps the destination clear. When you want the output to stay as a hash, the same method builds one directly by starting with an empty hash accumulator.
Building a new hash
items = { a: 1, b: 2, c: 3 }
doubled = items.each_with_object({}) do |(k, v), h|
h[k] = v * 2
end
# => { a: 2, b: 4, c: 6 }
The same method works well when the output should stay a hash. That makes it easy to rename keys, normalize values, or reshape the data without introducing a separate loop variable for the destination. Even a plain number can serve as the accumulator when the block performs arithmetic or tally-style work on each entry.
Using a counter
words = { cat: true, dog: true, elephant: true }
count = words.each_with_object(0) do |(word, _), n|
n += word.length
end
# count is the total length of all words (returns the final accumulator)
Even a plain number can act as the accumulator when the block is doing arithmetic or tally-style work. In that case, the object may be simple, but the pattern is still the same: read an entry, update the running result, move on.
common use cases
Grouping by a condition
users = { alice: 28, bob: 17, carol: 35, dave: 22 }
by_age_group = users.each_with_object({ "under 25" => [], "25+" => [] }) do |(name, age), groups|
if age < 25
groups["under 25"] << name
else
groups["25+"] << name
end
end
# => { "under 25" => [:bob, :dave], "25+" => [:alice, :carol] }
This version makes the grouping rule explicit, because the accumulator already contains both buckets. The block only decides which bucket should receive the current name. Flattening nested maps into a single-level structure follows the same approach, with the accumulator collecting expanded key-value pairs.
Flattening keys or values
config = { db: { host: "localhost", port: 5432 }, cache: { host: "redis", port: 6379 } }
flat = config.each_with_object({}) do |(section, values), result|
values.each do |key, value|
result["#{section}_#{key}".to_sym] = value
end
end
# => { :db_host => "localhost", :db_port => 5432, :cache_host => "redis", :cache_port => 6379 }
Flattening is another case where the accumulator reads naturally, because the output shape is visible in the empty hash at the call site. The block simply fills that hash with the transformed keys and values. Collecting specific computed fields from each entry produces a purpose-built summary array that keeps only what the next processing stage needs.
Collecting specific fields
products = {
widget: { price: 10, weight: 5, stock: 100 },
gadget: { price: 25, weight: 2, stock: 50 },
gizmo: { price: 15, weight: 8, stock: 75 }
}
summary = products.each_with_object([]) do |(name, attrs), list|
list << { name: name, value: attrs[:price] * attrs[:stock] }
end
# => [{ name: :widget, value: 1000 }, { name: :gadget, value: 1250 }, { name: :gizmo, value: 1125 }]
That pattern is useful when you want a smaller, purpose-built record from each entry. It keeps the original hash available for the read step, but the final array contains only the fields the next stage cares about.
each_with_object vs inject
Both accumulate a result across iterations. The difference is subtle:
# inject passes the return value of the block as the accumulator for the next iteration
scores.inject({}) do |memo, (name, score)|
memo[name] = score > 80 ? "pass" : "fail"
memo # must explicitly return memo
end
# each_with_object passes the same object every time — no need to return it
scores.each_with_object({}) do |(name, score), memo|
memo[name] = score > 80 ? "pass" : "fail"
end
each_with_object is cleaner when the accumulator is a collection you’re mutating directly. inject is more flexible — the block’s return value becomes the next accumulator, which is useful for arithmetic chains.
If the accumulator is mostly a container, each_with_object usually reads more directly. If the accumulator itself is meant to change shape on each step, inject gives you that extra room.
Gotchas
Block argument order. The first block argument is the key-value pair as a two-element array, the second argument is the memo object:
h.each_with_object({}) do |(key, value), memo| # correct
# NOT: |key, value, memo|
end
Forgetting the parentheses around key, value is a common mistake — without them, key receives the pair [:a, 1] and value is the memo object.
That argument order looks unusual the first time you meet it, but it matches the way Ruby yields a hash pair. Once you see the accumulator at the end, the block signature becomes easier to read.
Returns the passed object, not a new one. If you pass a hash as the accumulator and mutate it, the original hash is affected:
original = {}
result = { a: 1 }.each_with_object(original) do |(k, v), memo|
memo[k] = v * 2
end
result.object_id == original.object_id # => true
result # => { a: 2 }
original # => { a: 2 } - same object
That last example is a reminder that the accumulator is the object you handed in, not a copy. If you need a fresh destination, create one before the call and treat the returned object as the result you intended to build.
A final note: each_with_object works on any enumerable, so arrays, ranges, and custom collections all respond to it. The only requirement is that the block mutates the accumulator in place rather than returning a new value on each iteration.
See Also
- /reference/enumerable/enumerable-each-with-object/ — the Enumerable version (works on arrays and any enumerable)
- /reference/hash-methods/each/ — basic hash iteration without accumulation
- /guides/ruby-hash-tricks/ — practical hash patterns and transformations