← Reference · Nestor G Pestelos Jr · Print this page

Programming Languages · Ruby

Ruby Composite Sort Keys

Reference entry · last updated September 21, 2026

A Ruby composite sort key orders a collection by more than one field at once. It is formed by returning an Array from a sort_by block, where acceptance order follows the array element by element: the first element is the primary key, and each later element breaks a tie left by the ones before it. Ruby sorts ascending only, so a descending numeric field is expressed by negating it inside the key.[1][2]

First principles and definitions

Comparison and the spaceship operator

A sort needs a comparison for every pair of elements. Ruby's base comparison is the spaceship operator, which returns -1, 0, or 1 depending on whether the left operand is less than, equal to, or greater than the right, and nil when the two are incomparable. Enumerable#sort uses each element's own comparison unless a block is supplied.[2]

Arrays compare element-wise. Array#<=> compares the first elements of the two arrays, and as soon as that comparison is not zero it decides the whole result. Only when the first elements are equal does it compare the second, and so on.[3] That single rule is the mechanism behind a composite key.

What sort_by does

Enumerable#sort_by takes a block that maps each element to a key object, then sorts ascending by those keys. The key can be any comparable object, including an Array. Because the key is computed once per element instead of once per comparison, sort_by is the usual choice when the key is more expensive than a direct comparison.[1]

Both sort and sort_by return an Array. They are not guaranteed to be stable, which means elements whose keys compare equal may come out in any order.[1][2]

Building a composite key

Return an array from the block. The first element is the primary sort field and the second is the tiebreak, and each further element extends the same rule. The result is a single ascending sort that satisfies several orderings at once.

words = [["the", 3], ["parties", 2], ["law", 2]]

# count descending, then word ascending
words.sort_by { |word, count| [-count, word] }
# => [["the", 3], ["law", 2], ["parties", 2]]

When the block receives a Hash entry, the entry is a two element pair, so the block can destructure it into the key and the value with |word, count|. Sorting a hash this way returns an Array of pairs, which is already the shape such a result is usually reported in, so no to_h is needed.[4]

Descending a numeric key

sort_by sorts ascending and has no descending flag. For an ordered numeric field, negating the value reverses its order: a larger count becomes a smaller key, so it sorts first. This is exact for integers and floats and keeps the remaining key elements ascending.

Key expressionPrimary orderTiebreak
[count, word]count ascendingword ascending
[-count, word]count descendingword ascending

Negation only works when the field is a number that can be negated. A nil or a non-numeric value raises inside the block. Reversing the whole result with .reverse is not a substitute: it flips every key element, so the tiebreak becomes descending as well.

Ties and stability

Because the sort is not stable, two elements with equal keys can appear in either order. A deterministic result therefore needs an element that is unique across the collection as the final key component, such as an identifier or a name. When the atomic values are already distinct, the last key element makes the order total and no separate stability mechanism is required.[1][2]

Choosing a sort method

References

  1. ^ Ruby 2.6.10 core documentation, "Enumerable," sort_by. Free full text: ruby-doc.org/core-2.6.10/Enumerable.html
  2. ^ Ruby 2.6.10 core documentation, "Enumerable," sort. Free full text: ruby-doc.org/core-2.6.10/Enumerable.html
  3. ^ Ruby 2.6.10 core documentation, "Array," <=> element-wise comparison. Free full text: ruby-doc.org/core-2.6.10/Array.html
  4. ^ Ruby 2.6.10 core documentation, "Hash," enumeration in insertion order. Free full text: ruby-doc.org/core-2.6.10/Hash.html