rubyguides

Enumerable#max

The max method returns the largest element in an enumerable collection. It compares elements using the <=> operator (spaceship operator) to determine order.

Use max when the collection already has a meaningful order and you want the highest item from that order. Numbers, strings, and objects with a clear <=> method work well. If the rule depends on a field or calculation, the block form keeps that rule close to the call.

Basic Usage

Without arguments, max returns the single largest element:

[3, 1, 4, 1, 5, 9, 2, 6].max
# => 9

["banana", "apple", "cherry"].max
# => "cherry"

Beyond the single largest element, max accepts an integer argument to return a ranked list of the top N items. This is useful for leaderboards, top sellers, or any scenario where the ranking matters more than the absolute maximum.

getting the n largest elements

Pass an integer n to get the n largest elements as an array:

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

["banana", "apple", "cherry", "date"].max(2)
# => ["date", "cherry"]

The n form is useful when you want a ranked shortlist instead of a single value. It still returns a plain array, so the result can be displayed, sliced, or passed into another method without extra conversion.

custom comparison with a block

You can provide a block to define custom comparison logic:

[-5, 3, -1, 7, -2].max { |a, b| a.abs <=> b.abs }
# => 7

["hello", "world", "ruby"].max { |a, b| a.length <=> b.length }
# => "hello"

The block must return -1, 0, or 1 (using the spaceship operator convention).

Keep the comparison block small enough that the ordering rule is obvious. If you are choosing by price, score, or length, max_by may read more directly because it asks for the value to compare instead of a full pairwise comparison.

handling empty collections

max returns nil when called on an empty collection:

[].max
# => nil

[].max(2)
# => []

That difference is important when the collection may be empty. The single-value form gives you nil, while the multi-value form gives you an empty array. Check for the shape you expect before chaining methods on the result.

max vs max_by

These two methods are often confused but behave differently:

MethodWhat it compares
maxCompares elements by their actual value using <=>
max_byCompares elements by the result of the block
# max compares by value
[1, 2, 3].max
# => 3

# max_by compares by block result
["one", "two", "three"].max_by { |s| s.length }
# => "three"

Choosing between max and max_by depends on whether the comparison rule lives on the object itself or needs to be computed from a field. The examples below show both patterns with real data structures.

Practical Examples

The comparison block receives two elements and must return -1, 0, or 1 using the spaceship convention. When you are comparing by a single attribute like price or length, max_by often reads more directly because it asks for the value to compare rather than a full pairwise comparison.

finding the highest price

products = [
  { name: "Widget", price: 29.99 },
  { name: "Gadget", price: 49.99 },
  { name: "Doohickey", price: 19.99 }
]

expensive = products.max { |a, b| a[:price] <=> b[:price] }
expensive[:name]
# => "Gadget"

When the comparison rule is more specific than the default order, the block form lets you spell out exactly what “largest” means. For strings, you might compare by length rather than alphabetical position.

For strings, you might compare by length rather than alphabetical position, which changes what max considers the largest entry. The block form keeps this custom rule visible right at the call site.

finding the longest string

words = ["elephant", "cat", "giraffe", "ant"]

longest = words.max { |a, b| a.length <=> b.length }
# => "giraffe"

You can also ask for the top N items ranked by a custom comparison, which returns a sorted array of the largest entries. This works well when the collection contains structured data and the comparison targets a specific field.

getting the n most expensive items

products = [
  { name: "Widget", price: 29.99 },
  { name: "Gadget", price: 49.99 },
  { name: "Doohickey", price: 19.99 },
  { name: "Thingamajig", price: 39.99 }
]

products.max(2) { |a, b| a[:price] <=> b[:price] }
# => [{:name=>"Gadget", :price=>49.99}, {:name=>"Thingamajig", :price=>39.99}]

Custom objects can define their own comparison rules by implementing the spaceship operator. This lets max work on domain types without a block, because the class itself knows how to rank its instances.

When the class implements the spaceship operator, max automatically returns the highest-ranked instance using that comparison logic. In performance-sensitive code, max_by can be a better choice because it evaluates the block once per element rather than recomparing during a sort-like pass.

working with custom objects

class Player
  attr_reader :score

  def initialize(name, score)
    @name = name
    @score = score
  end

  def <=>(other)
    @score <=> other.score
  end

  def to_s
    @name
  end
end

players = [
  Player.new("Alice", 95),
  Player.new("Bob", 87),
  Player.new("Charlie", 92)
]

players.max
# => Alice (score: 95)

When the class implements <=>, max returns the object with the highest rank using the comparison logic already defined on the type. In performance-sensitive code, max_by can be a better choice because it evaluates the block once per element rather than re-comparing during a sort-like pass.

Performance Notes

max uses the <=> operator to compare elements. For arrays, it performs a single pass through the collection — O(n) time complexity.

When using max with a block, the comparison logic runs multiple times during the sort. For finding a single maximum, max_by is often more efficient since it only evaluates the block once per element.

In day-to-day code, max is clearest when the objects already know how to compare themselves. Use the block form for special rules and prefer max_by when you are selecting by one derived value.

See Also