rubyguides

Hash#sum

Hash#sum is defined in the Enumerable module, which Hash includes. It totals values — either by adding key-value pairs directly, or by summing whatever your block returns.

Basic Usage

With a block, you get proper key and value parameters:

scores = { alice: 95, bob: 82, carol: 98 }

scores.sum { |_name, score| score }
# => 275

The block receives both the key and the value for each pair, so you can choose which part to sum. Using _name with an underscore prefix signals that the key is not used in the calculation.

Shorthand with Symbol#to_proc

When you’re summing a single value from each pair, the &:method shorthand works:

prices = { "Widget" => 12.99, "Gadget" => 24.99, "Doodad" => 7.99 }

prices.sum(&:last)
# => 45.97

&:last calls .last on each [key, value] pair — which returns the value. Exactly what you need. The shorthand is concise but only works when you are summing the value portion of each pair directly.

Summing with initial value

Pass an initial value as the first argument:

scores.sum(100)
# Without block: 100 + [:alice, 95] + [:bob, 82] + [:carol, 98]
# => raises TypeError (can't add Integer + Array)

Without a block, Ruby tries to add the initial value to each key-value pair as a two-element array, which fails for most non-array types.

The initial value approach is only useful with a block:

scores.sum(10) { |_name, score| score }
# => 10 + 95 + 82 + 98 = 285

The initial value is added first, then each block result is added on top. This is useful for starting a running total from a base value like a carryover balance or a minimum threshold.

Without a Block

sum without a block iterates over key-value pairs as 2-element arrays and adds them together:

{a: 1, b: 2}.sum
# => [:a, 1, :b, 2]  — arrays concatenated, not values summed

This only makes sense when the concatenation itself is meaningful, or when all values are Integer (because [1, 2] + [3, 4] = [1, 2, 3, 4] and [1] + [2] = [1, 2]). For summing numeric values, always use a block.

Using blocks for conditional summing

The block gives you full control over what gets summed:

users = { alice: 28, bob: 17, carol: 35, dave: 22 }

# Sum only adults
users.sum(0) { |_name, age| age >= 18 ? age : 0 }
# => 85  (28 + 35 + 22)

The block returns 0 for underage entries, which adds nothing to the total. This pattern works for any filtering that can be expressed as a numeric value per entry.

Without 0 as initial value, the block approach still works because numbers have a natural sum:

users.sum { |_name, age| age >= 18 ? age : 0 }
# => 85

Omitting the initial value works because sum defaults to 0 and addition between integers is always valid. The initial value is only needed when the block might return a non-numeric first result.

Strings and Arrays

sum works with any objects that respond to +:

words = { a: "cat", b: "dog", c: "bat" }

words.sum("") { |_k, v| v }
# => "catdogbat"

The initial value "" gets the concatenation started. Without it, sum would default to 0 and the addition would fail because you cannot add an integer to a string.

Empty Hash

sum on an empty hash returns the initial value:

{}.sum
# => 0  (default initial value)

{}.sum(10)
# => 10

{}.sum(0) { |_k, v| v }
# => 0

An empty hash always returns the initial value, or 0 if none is given. This matches the mathematical convention that an empty sum is zero.

Why not use each or reduce?

You could write the same thing with each_with_object:

scores.each_with_object(0) { |(_name, score), total| total + score }

sum with a block is cleaner and more direct. Under the hood, it’s optimized with a C implementation that avoids the slower inject/reduce path when possible. The block-based form is also easier to read at a glance because the intent is immediately clear.

Gotchas

Without a block, sum on a hash concatenates arrays, not values:

{ a: 1, b: 2 }.sum  # => [:a, 1, :b, 2]  not 3

If you want the values, use a block.

Float precision: summing many floats can accumulate rounding errors. For financial calculations, use BigDecimal or a specialized library.

Strings are slow with sum: 'a'..'z').sum works but is O(n^2) because strings are immutable and each + creates a new string. Use join instead.

See Also