Enumerable#sort_by

Returns: Array · Added in v1.8.7 · Updated March 16, 2026 · Enumerable
ruby enumerable sorting ordering

The sort_by method sorts elements based on the return value of a block. It’s the flexible cousin of sort — instead of comparing elements directly, you tell Ruby how to compare them.

How It Works

sort_by evaluates the block for each element and sorts based on those return values:

collection.sort_by { |element| criteria }

Unlike sort, which compares elements using their own <=> method, sort_by lets you define custom comparison logic.

Basic Usage

Sort strings by length:

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

Sort hashes by a specific key:

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

sorted = people.sort_by { |person| person[:age] }
# => [{:name=>"Bob", :age=>25}, {:name=>"Alice", :age=>30}, {:name=>"Charlie", :age=>35}]

Sorting in Descending Order

Ruby doesn’t have a direct sort_by! (descending) option, but you can negate numeric values:

numbers = [5, 2, 8, 1, 9, 3]
sorted = numbers.sort_by { |n| -n }
# => [9, 8, 5, 3, 2, 1]

Or reverse after sorting:

words = ["cat", "elephant", "dog"]
sorted = words.sort_by(&:length).reverse
# => ["hippopotamus", "elephant", "dog", "cat"]

Multiple Sort Criteria

Sort by multiple criteria by returning an array from the block:

products = [
  { name: "Phone", category: "electronics", price: 699 },
  { name: "Shirt", category: "clothing", price: 29 },
  { name: "Laptop", category: "electronics", price: 999 },
  { name: "Pants", category: "clothing", price: 49 }
]

# Sort by category first, then by price within each category
sorted = products.sort_by { |p| [p[:category], p[:price]] }
# => [
#   {:name=>"Shirt", :category=>"clothing", :price=>29},
#   {:name=>"Pants", :category=>"clothing", :price=>49},
#   {:name=>"Phone", :category=>"electronics", :price=>699},
#   {:name=>"Laptop", :category=>"electronics", :price=>999}
# ]

This works because arrays are compared element-by-element in order.

Practical Examples

Sort Files by Modification Time

require 'pathname'

files = Dir.glob("*").map { |f| Pathname.new(f) }
sorted = files.select(&:file?).sort_by(&:mtime)

# Most recently modified first
sorted.reverse.each { |f| puts "#{f.mtime} - #{f}" }

Sort by String Length (Case-Insensitive)

words = ["Apple", "banana", "Cherry", "date"]

# Case-insensitive sorting
sorted = words.sort_by { |w| w.downcase }
# => ["Apple", "banana", "Cherry", "date"]

Sort by Multiple Attributes

employees = [
  { name: "Alice", department: "Engineering", level: 2 },
  { name: "Bob", department: "Sales", level: 1 },
  { name: "Charlie", department: "Engineering", level: 1 },
  { name: "Diana", department: "Sales", level: 2 }
]

# Sort by department, then by level (descending within department)
sorted = employees.sort_by { |e| [e[:department], -e[:level]] }

Natural Sort (Like File Explorers)

files = ["file1.txt", "file10.txt", "file2.txt"]

# Standard sort: file1, file10, file2
# Natural sort: file1, file2, file10
sorted = files.sort_by { |f| f.scan(/\d+/).first.to_i }
# => ["file1.txt", "file2.txt", "file10.txt"]

Comparison with sort

MethodUse When
sortNatural comparison, simple elements
sort_byCustom comparison logic, complex criteria
numbers = [5, 2, 8, 1, 9]

numbers.sort                   # => [1, 2, 5, 8, 9]
numbers.sort_by { |n| n }      # => [1, 2, 5, 8, 9] (same result)

# sort_by shines with custom logic
numbers.sort_by { |n| -n }    # => [9, 8, 5, 2, 1] (descending)
numbers.sort_by { |n| n.to_s }  # => [1, 2, 5, 8, 9] (string sort)

Performance Notes

  • sort_by uses the Schwartzian transform internally — it maps elements to their sort keys once, sorts, then extracts original elements
  • This is more efficient than sort with an expensive block that gets called repeatedly
  • When no block is given, Ruby returns an enumerator (Ruby 1.9+)
# Lazy evaluation
enum = [5, 2, 8].sort_by
enum.each { |n| puts n }  # Prints sorted values

Return Value

Returns a new array of sorted elements:

[3, 1, 4, 1, 5, 9, 2, 6].sort_by { |n| n }
# => [1, 1, 2, 3, 4, 5, 6, 9]

# With n argument (Ruby 3.0+)
[3, 1, 4, 1, 5].sort_by(2) { |n| n }
# => [1, 1]

Returns an empty array if the collection is empty:

[].sort_by { |x| x }  # => []

Edge Cases

Empty collections return an empty array:

[].sort_by(&:to_s)  # => []

Collections with one element return that element sorted:

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

Nil or false block returns are handled as-is in sorting:

items = [nil, false, 1, 0, "a", ""]
items.sort_by { |x| x.nil? ? 1 : 0 }  # nil values sort last
# => [1, 0, "a", "", false, nil]

See Also