Question
Given this Ruby array of hashes:
a = [
{ foo: "foo", bar: 2 },
{ foo: "foo", bar: 3 },
{ foo: "foo", bar: 5 }
]
How can I sort the array in descending order by the value stored under the :bar key?
Using:
a.sort_by { |h| h[:bar] }
sorts the hashes in ascending order. Negating the value works for numbers:
a.sort_by { |h| -h[:bar] }
but what is the clearest and most appropriate way to sort in descending order?
Short Answer
You will learn how Ruby's sort_by chooses an ordering, how to reverse an ascending sort for descending results, and when a custom sort block is a better fit. You will also see how to avoid changing the original array accidentally.
Concept
sort_by sorts collection items by a sort key returned from its block.
a.sort_by { |hash| hash[:bar] }
For every hash, Ruby extracts hash[:bar] as the key, then orders those keys from smallest to largest by default. That is why the result is ascending.
To get descending order, a clear general-purpose approach is to sort normally and then reverse the result:
descending = a.sort_by { |hash| hash[:bar] }.reverse
This works for numeric values, strings, dates, and other values that Ruby can sort. It communicates the intent directly: sort by :bar, then reverse the ordered result.
The alternative below is also valid when :bar is always numeric:
a.sort_by { |hash| -hash[:bar] }
Negative values reverse numeric order because, for example, -5 comes before -3 and -2. However, it is specific to numbers and can be less immediately readable than .reverse.
Mental Model
Think of sort_by as asking every hash to wear a badge containing its :bar value.
{ bar: 2 }gets badge2{ bar: 3 }gets badge3{ bar: 5 }gets badge5
Ruby lines up the badges from low to high by default: 2, 3, 5.
Calling .reverse asks Ruby to turn that completed line around: 5, 3, 2. The hashes move with their badges, producing descending order.
Syntax and Examples
Use sort_by to choose the field to sort on, then call reverse for descending order.
items = [
{ foo: "foo", bar: 2 },
{ foo: "foo", bar: 3 },
{ foo: "foo", bar: 5 }
]
sorted_items = items.sort_by { |item| item[:bar] }.reverse
p sorted_items
# [{:foo=>"foo", :bar=>5}, {:foo=>"foo", :bar=>3}, {:foo=>"foo", :bar=>2}]
sort_by does not modify items; it creates and returns a new sorted array. reverse also returns a new array.
For a numeric key, this shorter form is valid:
sorted_items = items.sort_by { |item| -item[:bar] }
For more direct control over the comparison direction, use sort with the spaceship operator (<=>):
sorted_items = items.sort { |left, right| right[:bar] <=> left[:bar] }
Normally, left[:bar] <=> right[:bar] means ascending. Swapping the operands makes it descending.
Step by Step Execution
Consider this code:
items = [
{ name: "Basic", score: 10 },
{ name: "Pro", score: 30 },
{ name: "Plus", score: 20 }
]
ordered = items.sort_by { |plan| plan[:score] }.reverse
Execution steps:
sort_byvisits{ name: "Basic", score: 10 }and extracts10.- It visits
{ name: "Pro", score: 30 }and extracts30. - It visits
{ name: "Plus", score: 20 }and extracts20. - Ruby sorts the hashes by those keys in ascending order: scores
10, 20, 30. .reversereturns the elements in the opposite order: scores30, 20, 10.orderedcontainsPro, thenPlus, thenBasic.
p ordered.map { |plan| plan[:name] }
# ["Pro", "Plus", "Basic"]
Real World Use Cases
Descending sorts are common when the largest, newest, or highest-priority value should appear first.
- Leaderboards: show users with the highest score first.
- E-commerce: show products with the highest rating, price, or sales count first.
- Dashboards: show the most recent events first when sorting by a timestamp.
- Reporting: list departments with the largest revenue first.
- Job queues: display highest-priority jobs before lower-priority jobs.
Example: sort API-like user data by reputation.
users = [
{ name: "Asha", reputation: 120 },
{ name: "Mina", reputation: 450 },
{ name: "Leo", reputation: 210 }
]
by_reputation = users.sort_by { |user| user[:reputation] }.reverse
Real Codebase Usage
In application code, make the selected field and direction easy to read.
posts_sorted_by_likes = posts.sort_by { |post| post[:likes] }.reverse
When the collection may contain invalid values, validate or normalize the key before sorting. For example, missing scores can be treated as zero:
ranked_users = users.sort_by { |user| user[:score] || 0 }.reverse
For a more complex ordering rule, use sort and a comparator. This is useful when descending order is only one part of the rule:
sorted_orders = orders.sort do |left, right|
by_priority = right[:priority] <=> left[:priority]
by_priority.zero? ? left[:created_at] <=> right[:created_at] : by_priority
end
This sorts by highest priority first, then by oldest creation time when priorities match.
If data comes from a database, prefer asking the database to sort when possible. For example, in Rails with Active Record:
Post.order(likes: :desc)
That avoids loading every record into Ruby solely to sort it.
Common Mistakes
Forgetting to save the returned array
sort_by does not mutate the original array.
items.sort_by { |item| item[:bar] }.reverse
p items # Still in its original order
Store the result instead:
items = items.sort_by { |item| item[:bar] }.reverse
Calling reverse before sorting
This only reverses the input's current order. It does not sort by :bar.
# Not a descending sort by :bar
items.reverse.sort_by { |item| item[:bar] }
Sort first, then reverse:
items.sort_by { |item| item[:bar] }.reverse
Negating a non-numeric value
This works only when the sort key is numeric.
products.sort_by { |product| -product[:name] }
# NoMethodError: undefined method `-@` for String
For strings or dates, use .reverse after sort_by:
products.sort_by { |product| product[:name] }.reverse
Using sort without returning a comparison value
A sort block must compare the two items and return an ordering result.
# Incorrect: the block returns an Integer, not a comparison between left and right
items.sort { |item| item[:bar] }
Use sort_by for a simple field, or compare both objects explicitly:
items.sort { |left, right| right[:bar] <=> left[:bar] }
Modifying an array unintentionally
Bang methods mutate the receiver:
items.reverse! # Changes items itself
Use non-bang methods when you want to retain the original order, or deliberately assign the result to a different variable.
Comparisons
| Approach | Example | Best use | Limitation |
|---|---|---|---|
sort_by then reverse | items.sort_by { |x| x[:bar] }.reverse | Clear descending ordering for any sortable key | Creates an intermediate ascending result before reversal |
| Negated sort key | items.sort_by { |x| -x[:bar] } | Concise numeric descending sorts | Only works for numeric keys |
| Custom comparator | items.sort { |a, b| b[:bar] <=> a[:bar] } | Custom or multi-rule comparisons | More verbose and easier to get wrong |
| In-place sorting | items.sort_by! { |x| x[:bar] }; items.reverse! | When changing the original array is intended | Mutates data and is less convenient as one expression |
For a single hash field, prefer sort_by. It expresses that Ruby should first calculate one key for each item. Use sort when the ordering needs to compare two items directly.
Cheat Sheet
# Ascending by :bar
items.sort_by { |item| item[:bar] }
# Descending by :bar: clear and general
items.sort_by { |item| item[:bar] }.reverse
# Descending by a numeric :bar key only
items.sort_by { |item| -item[:bar] }
# Descending with an explicit comparator
items.sort { |left, right| right[:bar] <=> left[:bar] }
# Mutate the original array deliberately
items.sort_by! { |item| item[:bar] }
items.reverse!
sort_bydefaults to ascending order.sort_byreturns a new array;sort_by!mutates the existing array..reverseworks with numbers, strings, dates, and other sortable keys.-valueworks only whenvalueis numeric.- Use
hash[:key]to read a value from a hash with a symbol key.
FAQ
How do I sort an array of hashes descending in Ruby?
Use sort_by to extract the key and call reverse:
items.sort_by { |item| item[:bar] }.reverse
Why does sort_by sort in ascending order?
Ruby's default ordering is ascending: smaller values come before larger values. sort_by follows that default using the key returned by its block.
Is sort_by { |item| -item[:bar] } valid Ruby?
Yes. It is valid and useful for numeric values. It is not generic, because strings, dates, and many other objects cannot be negated.
Does sort_by change the original array?
No. sort_by returns a new array. Use sort_by! only when you intentionally want to modify the original array.
How do I sort hashes by a string value in descending order?
Use the same pattern:
people.sort_by { |person| person[:name] }.reverse
Should I use sort or sort_by?
Use sort_by when sorting by one derived value, such as item[:bar]. Use sort when you need a custom comparison involving both items or multiple conditional rules.
What happens if some hashes do not have the sort key?
hash[:bar] returns nil. Comparing nil with numbers or strings usually fails, so choose a fallback value or filter invalid records before sorting.
Mini Project
Description
Build a small leaderboard formatter. Given players stored as hashes, sort them from highest to lowest score and print a numbered ranking. This mirrors the ordering used in game leaderboards, score reports, and ranking screens.
Goal
Produce a ranking in descending score order without changing the original players array.
Requirements
- Create an array containing at least four player hashes with
:nameand:scorekeys. - Sort players from highest score to lowest score.
- Keep the original array unchanged.
- Print each sorted player with a one-based rank, name, and score.
- Treat a missing score as
0.
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.