Question

Sort an Array of Hashes in Descending Order in Ruby

rubysorting

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 badge 2
  • { bar: 3 } gets badge 3
  • { bar: 5 } gets badge 5

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.

Take Quiz

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:

  1. sort_by visits { name: "Basic", score: 10 } and extracts 10.
  2. It visits { name: "Pro", score: 30 } and extracts 30.
  3. It visits { name: "Plus", score: 20 } and extracts 20.
  4. Ruby sorts the hashes by those keys in ascending order: scores 10, 20, 30.
  5. .reverse returns the elements in the opposite order: scores 30, 20, 10.
  6. ordered contains Pro, then Plus, then Basic.
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
Take Quiz

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

ApproachExampleBest useLimitation
sort_by then reverseitems.sort_by { |x| x[:bar] }.reverseClear descending ordering for any sortable keyCreates an intermediate ascending result before reversal
Negated sort keyitems.sort_by { |x| -x[:bar] }Concise numeric descending sortsOnly works for numeric keys
Custom comparatoritems.sort { |a, b| b[:bar] <=> a[:bar] }Custom or multi-rule comparisonsMore verbose and easier to get wrong
In-place sortingitems.sort_by! { |x| x[:bar] }; items.reverse!When changing the original array is intendedMutates 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.

Take Quiz

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_by defaults to ascending order.
  • sort_by returns a new array; sort_by! mutates the existing array.
  • .reverse works with numbers, strings, dates, and other sortable keys.
  • -value works only when value is 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.

Take Quiz

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

  1. Create an array containing at least four player hashes with :name and :score keys.
  2. Sort players from highest score to lowest score.
  3. Keep the original array unchanged.
  4. Print each sorted player with a one-based rank, name, and score.
  5. Treat a missing score as 0.
Take Quiz

Keep learning

Related questions

View more