rubyguides

Enumerable#min_by

The min_by method returns the element that would be smallest according to the criteria you define in a block. Unlike min which compares elements directly, min_by lets you specify how to compare them.

how it works

min_by evaluates the block for each element and returns the element that produced the smallest value:

collection.min_by { |element| block_return_value }

The element itself is returned, not the block’s return value.

That separation keeps the method easy to reason about. The block defines the ranking rule, and min_by handles the search, so the calling code can stay focused on the outcome instead of the mechanics.

basic usage

Find the shortest string in an array:

words = ["cat", "elephant", "dog", "hippopotamus"]
shortest = words.min_by { |word| word.length }
# => "cat"

The block { |word| word.length } produces the character count of each string, and min_by returns the word that produced the smallest count. The method returns the original element, not the block value, so you get "cat" rather than the number 3.

Find the youngest person in a collection:

people = [
  { name: "Alice", age: 30 },
  { name: "Bob", age: 25 },
  { name: "Charlie", age: 35 }
]

youngest = people.min_by { |person| person[:age] }
# => {:name=>"Bob", :age=>25}

These examples show the most common shape of min_by: pick the thing you want to keep, then describe how to rank it. The block can look at a nested field, a computed length, or any other value that can be compared with <=>.

with nested comparisons

When multiple elements produce the same minimum value, min_by returns the first one encountered:

words = ["aa", "bb", "cc", "dd"]
result = words.min_by { |w| w[0] }
# => "aa" (all have the same value, first one wins)

When several elements tie, min_by returns the first one it sees. That makes the result predictable, which matters when the data comes from an ordered list or when two records can legitimately share the same score.

finding multiple minimum elements

Use min_by with an argument to get multiple minimum elements:

numbers = [5, 2, 8, 1, 9, 3, 7, 4, 6]

smallest_three = numbers.min_by(3) { |n| n }
# => [1, 2, 3]

The n argument turns the method into a small ranking tool. Instead of only asking for the smallest element, you can keep several of the smallest values and use them for reports or dashboards.

This is useful for finding top performers by a specific metric:

products = [
  { name: "Laptop", price: 999 },
  { name: "Phone", price: 699 },
  { name: "Tablet", price: 449 },
  { name: "Watch", price: 299 },
  { name: "Headphones", price: 199 }
]

cheapest_three = products.min_by(3) { |p| p[:price] }
# => [{:name=>"Headphones", :price=>199}, {:name=>"Watch", :price=>299}, {:name=>"Tablet", :price=>449}]

That pattern is useful when you need a shortlist rather than a single winner. It keeps the comparison logic in one place and lets Ruby do the ordering work for you. The result array preserves the order of the original elements, so the cheapest three products appear in the sequence they were encountered.

with arrays of arrays

pairs = [[1, 5], [3, 2], [2, 8], [4, 1]]

# Find pair with smallest first element
by_first = pairs.min_by { |pair| pair[0] }
# => [1, 5]

# Find pair with smallest second element
by_second = pairs.min_by { |pair| pair[1] }
# => [4, 1]

Array pairs are a nice demonstration because they make the selection rule easy to see. The block can reference any index, which means the same data structure can be ranked by different dimensions depending on which element you select. This flexibility is what makes min_by a natural fit for multi-field data.

practical examples

finding the shortest method name

methods = [:to_s, :inspect, :class, :object_id, :nil?]
shortest = methods.min_by { |m| m.to_s.length }
# => :nil? (4 characters)

This kind of example is small, but it shows the main advantage of min_by: the ranking logic stays close to the data. You do not need a separate sort or loop when all you want is the best match by one metric.

finding the cheapest shipping option

shipping_options = [
  { carrier: "USPS", weight: 5, distance: 100 },
  { carrier: "FedEx", weight: 5, distance: 50 },
  { carrier: "UPS", weight: 5, distance: 75 }
]

cheapest = shipping_options.min_by { |opt| opt[:weight] * opt[:distance] * 0.5 }
# => {:carrier=>"FedEx", :weight=>5, :distance=>50}

The scoring expression can be as simple or as custom as you need. That flexibility is why min_by shows up in scheduling, routing, and “best candidate” style code. The block can combine multiple fields into a single numeric score, and min_by handles the rest.

finding the nearest location

current_location = { x: 10, y: 10 }

locations = [
  { name: "Office", x: 5, y: 5 },
  { name: "Home", x: 15, y: 15 },
  { name: "Store", x: 8, y: 12 }
]

nearest = locations.min_by { |loc| 
  (loc[:x] - current_location[:x]).abs + (loc[:y] - current_location[:y]).abs 
}
# => {:name=>"Office", :x=>5, :y=>5}

This is a classic use case for min_by: turn the thing you care about into a numeric score, then let Ruby pick the smallest score for you. The method stays readable even when the score formula gets a little longer.

comparison with min

MethodUse When
minYou want natural/built-in comparison
min_byYou need custom comparison logic
numbers = [5, 2, 8, 1, 9]

numbers.min        # => 1 (natural comparison)
numbers.min_by { |n| n }  # => 1 (same result)

# With custom logic, min_by shines
numbers.min_by { |n| -n }  # => 9 (finds maximum via negative)

Use min_by whenever the comparison should be driven by a block instead of the objects themselves. That keeps the code from leaking sorting details into the rest of the method.

performance notes

  • min_by uses the spaceship operator (<=>) internally for comparisons
  • For enumerables that respond to #<=>, this is efficient
  • When no block is given, Ruby 3.0+ returns an enumerator
# Ruby 3.0+ - lazy evaluation
enum = [5, 2, 8, 1, 9].min_by
enum.each { |n| puts n }  # Still evaluates, but deferred

The enumerator form is mostly useful when you want to compose the call with something else. For ordinary minimum lookups, the direct block form is usually clearer.

Return Value

Returns the element that minimizes the block’s return value, or nil if the collection is empty:

[].min_by { |x| x }  # => nil

When the collection is empty, min_by returns nil rather than raising an error. This is a safe default that lets the caller handle the empty case with a conditional or a fallback value without wrapping the call in a rescue block.

With n argument, returns an array of n elements:

[3, 1, 4, 1, 5, 9, 2, 6].min_by(2)
# => [1, 1]

When you ask for multiple results, Ruby returns them in sorted order. That makes the answer easy to scan and easy to feed into a later step that expects the smallest values first. The n argument changes the return type from a single element to an array.

edge cases

Empty collections return nil:

[].min_by(&:to_s)  # => nil

The &:to_s shorthand passes each element to to_s before comparing, which works for any collection where the string representation produces a meaningful ordering. When the array is empty, the result is still nil regardless of the block logic.

Collections with one element return that element:

[42].min_by { |n| n }  # => 42

The method behaves predictably at the boundaries, which makes it a good fit for general-purpose collection processing. Empty input gives nil, and one-item input gives the only item available.

See Also