Hash#max_by
Hash#max_by iterates over a hash and returns the key-value pair whose block value is the largest. It’s defined in the Enumerable module, which Hash includes.
scores = { alice: 95, bob: 82, carol: 98 }
scores.max_by { |_name, score| score }
# => [:carol, 98]
Without a block, max_by uses the key itself for comparison. With a block, it uses the block’s return value.
Basic Usage
products = { "Widget" => 12.99, "Gadget" => 24.99, "Doodad" => 7.99 }
cheapest = products.min_by { |_name, price| price }
# => ["Doodad", 7.99]
priciest = products.max_by { |_name, price| price }
# => ["Gadget", 24.99]
max_by returns the key-value pair as a two-element array, not the value alone.
Controlling What Gets Compared
The block decides the comparison. This means you can maximize on any attribute, not just the hash values:
employees = {
"101" => { name: "Alice", tenure: 6 },
"102" => { name: "Bob", tenure: 2 },
"103" => { name: "Carol", tenure: 8 },
}
employees.max_by { |_id, data| data[:tenure] }
# => ["103", {:Name=>"Carol", :tenure=>8}]
When values are equal, max_by returns the first pair it encountered at that maximum:
{ a: 1, b: 1, c: 2 }.max_by { |_k, v| v }
# => [:c, 2] — unique max
Getting the Top N Pairs
max_by takes an optional n argument to return multiple pairs, sorted descending:
top_3 = products.max_by(3) { |_name, price| price }
# => [["Gadget", 24.99], ["Widget", 12.99], ["Doodad", 7.99]]
Without a block, it compares keys:
{ c: 3, a: 1, b: 2 }.max_by(2)
# => [[:c, 3], [:b, 2]]
The returned value is an array of arrays when n is specified.
Symbol#to_proc Shorthand
Ruby lets you skip the block when comparing a single attribute with the &: shorthand:
# Long form
products.max_by { |_name, price| price }
# Short form — exactly equivalent
products.max_by(&:last)
# => ["Gadget", 24.99]
&:last calls .last on each key-value pair. Since .last on a two-element array returns the value, this works perfectly for hashes where you want the largest value.
With Empty Hash
Calling max_by on an empty hash returns nil:
{}.max_by { |_k, v| v }
# => nil
If you pass n > 0, it returns an empty array:
{}.max_by(3) { |_k, v| v }
# => []
Handle empty hashes explicitly if the result matters to your logic.
Default Values and Missing Keys
max_by only iterates over keys that actually exist in the hash. If you have default values set via Hash.new(0), those are only present when accessed:
votes = Hash.new(0)
votes[:alice] += 1
votes[:bob] += 1
votes.max_by { |_k, v| v }
# => [:alice, 1] or [:bob, 1] — whichever is last in iteration order
For n > 0 with all equal values, iteration order determines which pairs are returned.
Enumerable vs Hash#max
Hash also has a Hash#max method that uses natural comparison on key-value pairs:
{ c: 3, a: 1, b: 2 }.max
# => [:c, 3]
products.max
# ArgumentError: comparison of Array with Array failed
Hash#max fails when you try to compare heterogeneous values. max_by with a block never compares the pairs directly — it compares only the block’s return value, so it’s safe for any hash structure.
Finding Multiple Maxima
To get all keys tied for the maximum value:
scores = { alice: 98, bob: 98, carol: 95 }
scores.group_by { |_k, v| v }.max_by { |_score, group| score }.last
# => [{:alice, 98}, {:bob, 98}]
This groups by value, then finds the highest value group, then returns all members.
Performance
max_by traverses the hash once. Complexity is O(n) where n is the hash size. This is the correct approach — sorting the hash first (O(n log n)) to find the maximum is wasteful.
See Also
- /reference/hash-methods/hash-min-by/ — the inverse: find the pair with the smallest block value
- /reference/hash-methods/has-key/ — check whether a key exists before calling
max_by - /guides/ruby-working-with-hashes/ — practical patterns for everyday hash manipulation