Array#sort
.sort and .sort_by arrange array elements in order. Use these when you need to organize data for display, search, or further processing. Both methods return a new array, leaving the original untouched.
Syntax
array.sort
array.sort { |a, b| block }
array.sort_by
array.sort_by { |element| block }
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| block | Block | See description | For .sort: compares two elements (a, b). For .sort_by: returns the sort key for each element. Without a block, elements are compared using the <=> operator. |
.sort
Basic usage
Without a block, .sort compares elements using the <=> operator (spaceship). Numbers and strings both define this method, so the default comparison works without extra setup:
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort
# => [1, 1, 2, 3, 4, 5, 6, 9]
words = ["banana", "apple", "cherry"]
words.sort
# => ["apple", "banana", "cherry"]
Default sorting without a block is the most direct option when the elements already have a natural order. It keeps the call short and makes the sorting rule obvious: Ruby compares the objects the same way it would compare them anywhere else.
Custom order with a block
The block receives two elements and should return:
- A negative number when the first should come first
- Zero when they’re equal (order is undefined)
- A positive number when the second should come first
# Reverse order
[3, 1, 2].sort { |a, b| b <=> a }
# => [3, 2, 1]
# Sort by absolute value
[-5, -3, 1, 2].sort { |a, b| a.abs <=> b.abs }
# => [1, 2, -3, -5]
Using <=> inside a sort block returns -1, 0, or 1 to signal the relative order of two values, and because it is already a binary method on comparable objects, wrapping it in a block is the standard way to write a custom sort. The shorthand b <=> a flips the default direction without any extra ceremony.
The <=> shorthand makes this cleaner:
# Ascending (default)
[3, 1, 2].sort { |a, b| a <=> b }
# => [1, 2, 3]
# Descending
[3, 1, 2].sort { |a, b| b <=> a }
# => [3, 2, 1]
Blocks are a better fit when the sort rule depends on a calculation or a derived value. In those cases, the comparison logic sits right next to the call, so readers do not have to hunt through another method to understand why the data appears in a particular order.
.sort_by
Basic usage
.sort_by takes a block that returns a value to sort by. This is often simpler than writing custom comparison logic:
words = ["apple", "banana", "cherry", "date"]
# Sort by length
words.sort_by(&:length)
# => ["date", "apple", "banana", "cherry"]
# Equivalent to:
words.sort_by { |w| w.length }
# => ["date", "apple", "banana", "cherry"]
sort_by is especially handy when the sort key is easy to describe but expensive to compute. Ruby evaluates the block once per element, so the cost stays predictable even when the collection is large.
When .sort_by shines
The key advantage of .sort_by is efficiency when the sort key is expensive to compute. It evaluates the block once per element (caching the result), whereas .sort may call the block many times during comparison.
files = ["report.pdf", "script.rb", "data.json", "readme.md"]
# Sort by file extension - simple and readable
files.sort_by { |f| File.extname(f) }
# => [".json", ".md", ".pdf", ".rb"]
# Sort by length, reversed
files.sort_by { |f| -f.length }
# => ["readme.md", "report.pdf", "script.rb", "data.json"]
Because sort_by stores the computed keys before comparing, it handles expensive computations gracefully and gives you a predictable cost per element regardless of the collection size. This also means the block can be as simple or as involved as the sort key demands without worrying about repeated calls.
Sorting Hashes
prices = { apple: 1.50, banana: 0.75, orange: 2.00 }
# Sort by value
prices.sort_by { |_name, price| price }
# => [[:banana, 0.75], [:apple, 1.50], [:orange, 2.00]]
# Sort by key (symbol name)
prices.sort_by { |name, _price| name.to_s }
# => [:apple, :banana, :orange]
When sorting hashes, sort_by returns an array of key-value pairs rather than a new hash, so you may need to call .to_h on the result if you want the sorted data back in hash form. This extra step is worth remembering when the sorted output feeds into code that expects a hash.
Performance comparison
For simple comparisons, .sort is faster. For complex keys, .sort_by wins because it computes each key once:
# .sort - compares many times
large_array.sort { |a, b| a[:expensive_field] <=> b[:expensive_field] }
# .sort_by - computes key once per element
large_array.sort_by { |e| e[:expensive_field] }
In practice, the readability of .sort_by usually outweighs the minor performance difference unless you’re sorting millions of elements.
When you care about the clearest possible intent, start with sort_by and switch to sort only when the comparison rule itself is the more natural expression. That balance often keeps code simpler without sacrificing correctness.
Gotchas
Unstable sort
Both methods produce unstable sorts. When two elements are equal (block returns zero), their relative order is not guaranteed:
# May return [1, 1, 2] or [1, 1, 2] - either is valid
[1, 1, 2].sort_by { |x| x }
Ruby does not guarantee that equal elements keep their original positions after sorting, which means the order of ties can change between runs or Ruby versions. When the input order matters for equal keys, you can chain .sort_by.with_index to use the original index as a tiebreaker.
Missing comparison method
Elements must define <=> or you must provide a block:
[Object.new, Object.new].sort
# => ArgumentError: comparison of Object with Object failed
# Works with a block
[Object.new, Object.new].sort { |a, b| a.object_id <=> b.object_id }
# Works - sorts by object_id
When a class does not define <=>, the sort call raises an ArgumentError because Ruby has no way to compare the objects. Providing a block sidesteps this entirely by letting you pick any comparison logic you like, including falling back to object_id for a stable but arbitrary order.
Symbol#to_proc only works for methods
You can’t pass arguments to the method when using &:method:
# This works - length takes no arguments
words.sort_by(&:length)
# This does NOT work - to_i takes an argument
["1", "2"].sort_by(&:to_i)
# Use a block instead:
["1", "2"].sort_by { |s| s.to_i }
See Also
.max / .min— Find extreme values (often used with sorted data).sample— Get random elements (useful for shuffling).map— Transform elements (often combined with sort_by)