Enumerable#sort_by
Array · Added in v1.8.7 · Updated March 16, 2026 · Enumerable 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
| Method | Use When |
|---|---|
sort | Natural comparison, simple elements |
sort_by | Custom 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_byuses the Schwartzian transform internally — it maps elements to their sort keys once, sorts, then extracts original elements- This is more efficient than
sortwith 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
Enumerable#max_by— find the maximum element by a criteriaEnumerable#min_by— find the minimum element by a criteriaEnumerable#group_by— group elements by a criteriaArray#sort— natural sorting for arrays