rubyguides

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.

Signature

each_with_object(obj) { |(key, value), memo| block } -> obj
each_with_object(obj) -> Enumerator

Parameters

ParameterDescription
objThe initial value of the accumulator — any object. Returned after iteration completes.

Return Value

Returns the passed obj after processing all entries. The original hash is unchanged.

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"]

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 }

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)

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] }

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 }

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 }]

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.

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.

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!)

See Also