Hash#min_by

Updated April 21, 2026 · Hash Methods
ruby hash enumerable min max sorting

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