Hash#min_by
Overview
min_by is defined on Enumerable, so it works on hashes as well as arrays. When called on a hash, it evaluates the block for each key-value pair and returns the pair that produced the smallest value. If multiple pairs tie for the minimum, the first one encountered is returned.
Unlike Hash#min which compares hash keys or values directly, min_by lets you derive a comparison value from each pair.
Signature
min_by { |(key, value)| block } -> [key, value] | nil
min_by(n) { |(key, value)| block } -> Array | nil
min_by -> Enumerator
Return Value
Returns a two-element array [key, value] for the pair that minimizes the block result. Returns nil if the hash is empty.
Basic Usage
Finding the minimum by a value attribute
servers = {
web1: { cpu: 45, memory: 80 },
web2: { cpu: 12, memory: 90 },
web3: { cpu: 33, memory: 55 }
}
servers.min_by { |_name, attrs| attrs[:cpu] }
# => [:web2, { cpu: 12, memory: 90 }]
Using a symbol-to-proc shorthand
scores = { alice: 95, bob: 82, carol: 78 }
scores.min_by { |_name, score| score }
# => [:carol, 78]
# equivalent using Enumerable#min_by on array of pairs
scores.min_by(&:last)
# => [:carol, 78]
With custom comparison logic
files = {
report: { size: 1024, modified: Time.new(2024, 1, 1) },
data: { size: 8192, modified: Time.new(2024, 6, 15) },
log: { size: 2048, modified: Time.new(2024, 3, 10) }
}
# Find the smallest file
files.min_by { |_name, attrs| attrs[:size] }
# => [:report, { size: 1024, modified: ... }]
# Find the most recently modified
files.min_by { |_name, attrs| -attrs[:modified].to_i }
# => [:data, { size: 8192, modified: ... }]
Common Use Cases
Finding the cheapest or lightest option
products = {
basic: { price: 29, weight: 200 },
standard: { price: 49, weight: 350 },
premium: { price: 89, weight: 150 }
}
# Find the lightest product
products.min_by { |_name, attrs| attrs[:weight] }
# => [:premium, { price: 89, weight: 150 }]
# Find the cheapest product
products.min_by { |_name, attrs| attrs[:price] }
# => [:basic, { price: 29, weight: 200 }]
Finding the closest match
users = {
alice: { lat: 51.5074, lon: -0.1278 }, # London
bob: { lat: 40.7128, lon: -74.0060 }, # New York
carol: { lat: 35.6762, lon: 139.6503 } # Tokyo
}
def distance(lat1, lon1, lat2, lon2)
Math.sqrt((lat1 - lat2)**2 + (lon1 - lon2)**2)
end
# Find the user closest to London (51.5, -0.1)
closest = users.min_by do |_name, loc|
distance(loc[:lat], loc[:lon], 51.5, -0.1)
end
# => [:alice, { lat: 51.5074, lon: -0.1278 }]
Finding the shortest or fastest route
routes = {
via_north: { distance: 450, time: 6.5 },
via_coast: { distance: 380, time: 8.0 },
via_mountain: { distance: 320, time: 9.5 }
}
# Find the fastest route
routes.min_by { |_name, info| info[:time] }
# => [:via_north, { distance: 450, time: 6.5 }]
# Find the shortest route
routes.min_by { |_name, info| info[:distance] }
# => [:via_mountain, { distance: 320, time: 9.5 }]
Using with negative values for “maximum” behavior
Since there’s no built-in max_by on Hash directly in some Ruby versions, min_by with a negated value works:
scores = { alice: 95, bob: 82, carol: 78 }
# Using negative to find maximum
scores.min_by { |_name, score| -score }
# => [:alice, 95]
Getting Multiple Minimums
Pass a number to return the n smallest pairs:
products = {
widget: { price: 10 },
gadget: { price: 25 },
gizmo: { price: 15 },
doohickey: { price: 30 }
}
# Find the two cheapest products
products.min_by(2) { |_name, attrs| attrs[:price] }
# => [[:widget, { price: 10 }], [:gizmo, { price: 15 }]]
Return Value When Empty
{}.min_by { |k, v| v }
# => nil
No exception is raised for an empty hash — it returns nil.
Comparison with Alternatives
vs Enumerable#min
# min_by works on the value (or block result) directly
scores = { alice: 95, bob: 82, carol: 78 }
scores.min_by { |_k, v| v } # => [:carol, 78]
# min without a block compares keys
scores.min # => [:alice, 95]
# min with a block compares by block result
scores.min { |a, b| a.last <=> b.last } # => [:carol, 78]
vs sort.first
sort arranges the entire hash, which is O(n log n). min_by only makes a single pass, which is O(n):
# Efficient — single pass
scores.min_by { |_k, v| v }
# Wasteful — sorts everything first
scores.sort { |a, b| a.last <=> b.last }.first
For finding a single minimum, min_by is always the better choice.
Gotchas
Returns a two-element array, not a single value. If you want just the key or just the value, destructure it:
key, value = scores.min_by { |_k, v| v }
key # => :carol
value # => 78
Block argument order for hashes. When iterating a hash, the block receives the key first, then the value:
h.each { |k, v| puts "#{k}: #{v}" } # a: 1, b: 2
This is consistent with each, map, and other hash iterators.
Tie-breaking is deterministic but unspecified. If two pairs produce the same block value, Ruby returns the one it encountered first during iteration. Iteration order over hashes (prior to Ruby 3.2+) was not guaranteed to be consistent. In modern Ruby, hash iteration order is preserved, but don’t rely on which tied pair wins if ties matter to you.
See Also
- /reference/enumerable/enumerable-min-by/ — the Enumerable version, works on arrays and hashes
- /reference/enumerable/enumerable-max-by/ — find the maximum by a derived value
- /guides/ruby-hash-tricks/ — practical patterns for transforming and selecting from hashes