Array#min_by: pick the smallest element by a derived key
min_by { |element| ... } Array#min_by returns the element of an array whose block-returned key is the smallest. Despite the name, the method is not defined directly on Array. It comes from the Enumerable module, mixed in by Array at load time. That detail matters: the same call on a hash, range, or any class that includes Enumerable runs the same internal code, and there is no Array-native fast path to promise.
The common use is picking a record by a derived attribute, like “the user with the lowest age” or “the shortest word in a list.” If you can express the criterion as a block that returns something comparable, min_by finds it in a single pass.
Syntax
arr.min_by # => Enumerator (no block given)
arr.min_by { |element| key } # => element, or nil if empty
arr.min_by(n) # => Enumerator over n smallest keys
arr.min_by(n) { |element| key } # => Array of n elements, sorted ascending by key
The n argument is optional. When present, the return shape changes from a single element to an Array of n elements sorted ascending by the block value. Without a block, you get an Enumerator you can chain on.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
n | Integer | nil | Number of smallest elements to return. 0 returns []. n >= arr.size returns the whole array sorted ascending by the block value. |
| block | Block | required for a result | Takes one element and returns the comparison key. The key must respond to <=> (and -@ for the n-form). |
The n argument was added in Ruby 2.2. The block-only form of min_by has been around since Enumerable#min_by landed in 1.8.
Array#min_by examples
Pick the shortest string
words = %w[cat elephant bat ant]
words.min_by(&:length)
# => "cat"
Length-based selection is the textbook case. The block returns an integer, integers compare with <=>, and you get the answer in one pass. The &:length shorthand works because Symbol#to_proc builds a block that calls length on each element.
Find a record by attribute
users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 55 },
{ name: "Grace", age: 85 }
]
users.min_by { |u| u[:age] }
# => {:name=>"Ada", :age=>36}
The block returns whatever you want to compare on. The method returns the element itself, not the key, so the hash is what comes back, not 36.
Get the n smallest by key
prices = { apple: 1.50, banana: 0.75, orange: 2.00, mango: 3.50 }
pairs = prices.to_a
pairs.min_by(2) { |_name, price| price }
# => [[:banana, 0.75], [:apple, 1.5]]
The result is already sorted ascending by the block value, so you don’t have to call .sort on it. Passing a number larger than the array returns the whole array, still sorted.
No block returns an Enumerator
enum = [3, 1, 4, 1, 5, 9, 2, 6].min_by
# => #<Enumerator: ...>
enum.each { |n| -n }
# => 1 # 9, because -9 is the smallest key
Unlike Array#first or Array#sample, calling min_by without a block does not raise. It returns an Enumerator, which is the lazy form. You can chain key choice with something else (like .with_index) without computing the key twice.
[3, 1, 4, 1, 5, 9, 2, 6].min_by.with_index { |n, i| n + i * 0.0 }
# => 1 # ties on key; returns the first tied element
Chained off an Enumerator, min_by reads keys that depend on the iteration position without re-walking the array. The next two examples show what happens with empty input and unusual values of n.
Empty arrays
[].min_by { |x| x }
# => nil
[].min_by(3) { |x| x }
# => []
The single-element form returns nil on an empty array. The n-element form returns []. The two forms handle emptiness differently, so check whichever return value your code actually expects.
n edge cases
[1, 2, 3].min_by(0) { |n| n }
# => []
[1, 2].min_by(5) { |n| n }
# => [1, 2]
n == 0 short-circuits to []. n larger than the array returns the whole array, still sorted ascending by the block value. You do not need to clamp the argument yourself.
Ties return the first encountered element
%w[ant bee cat dog].min_by { |w| w.length }
# => "ant"
Three elements tie at length 3, and ant is the first one in the array. That is the current MRI behavior, but the Ruby specification does not promise a particular tie-breaking order, so do not depend on it across Ruby versions or alternative implementations like JRuby or TruffleRuby.
Common gotchas
The block key must be comparable
[Object.new, Object.new].min_by { |o| o }
# => ArgumentError: comparison of Object with Object failed
Plain Object instances don’t define <=>, so the comparison blows up at runtime. Map to a comparable key (often object_id) to make the error go away cleanly:
[Object.new, Object.new].min_by { |o| o.object_id }
# => #<Object:0x...> # the first one, by object_id
For the n-form, the key also has to respond to -@ (unary minus), which the implementation uses to flip sign on a min-heap entry.
Where the method actually comes from
Array#min_by runs the same C function as (1..10).min_by because Array doesn’t define its own version. The only Array-native extremes are min, max, and minmax (Ruby 2.4+). Don’t write min_by thinking it’s faster on arrays; it isn’t, and the call goes through the Enumerable path. If you do need the Array-native path, use min or max with a two-argument comparator block.
n must be a non-negative integer
Passing a negative number, a float, or a string raises ArgumentError. The argument is checked before the method starts walking the array. If the count comes from user input, coerce or validate it first.
You get the element, not the key
min_by returns the array element, not whatever the block returned. If you need both the element and its key, use minmax_by (both extremes in one pass) or recompute the key after the call:
youngest = users.min_by { |u| u[:age] } # => the user hash
youngest, oldest = users.minmax_by { |u| u[:age] } # => [youngest, oldest]
Comparison with alternatives
min_by vs sort_by.first
sort_by sorts the entire array, which is O(n log n), then .first picks the smallest. min_by does the same job in O(n) and avoids materializing the sorted array. For large collections, the difference is real:
data = [3, 1, 4, 1, 5, 9, 2, 6]
data.min_by { |n| n } # => 1, O(n)
data.sort_by { |n| n }.first # => 1, O(n log n)
Reach for min_by unless you actually need the full sorted array for something downstream.
min_by vs min { |a, b| ... }
min takes a two-argument comparator block. min_by takes a one-argument key extractor. If you find yourself writing min { |a, b| f(a) <=> f(b) }, switch to min_by { |a| f(a) }. The min_by form evaluates f once per element, while the comparator form may call f many times during the sort process. This matters when the key is expensive to compute, like a regex match, a database lookup, or a derived score.
# Equivalent results, different costs:
items.min { |a, b| score(a) <=> score(b) } # score called many times
items.min_by { |a| score(a) } # score called once per element
Performance note
Enumerable#min_by was reimplemented in C in Ruby 2.5, which made it roughly 50% faster than the old pure-Ruby version. The current implementation uses an internal min-heap for the n-argument form, so min_by(n) is O(n log n) but with a small constant when n is small. For the single-element form, the cost is just O(n).
min_by is non-destructive. The original array is never touched, and you can call it inside chains or pipelines without cloning first.
Conclusion
Array#min_by is the right tool when the natural sort order isn’t the one you want. Hand it a block that returns a comparable key, and it gives back the element with the smallest key in a single pass. Add an n argument to get the n smallest as a pre-sorted array. Use the Enumerator form to defer the key choice or chain it with other lazy methods like .with_index. The method comes from Enumerable, so the same syntax works on hashes, ranges, and any class that includes the module. If you find yourself reaching for sort_by.first to grab a single extreme, switch to min_by and skip the sort.
See Also
- Array#max — direct array versions of
min,max, andminmax - Array#sort — the
sort_by.firstalternative for when you need the full sort - Enumerable#min_by — the canonical home of the method, with examples on hashes and ranges