Three flavors of sorting Go slices
There are primarily three ways of sorting slices in Go. Early on, we had the verbose but flexible method of implementing sort.Interface to sort the elements in a slice. Later, Go 1.8 introduced sort.Slice to reduce boilerplate with inline comparison functions. Most recently, Go 1.21 brought generic sorting via the slices package, which offers a concise syntax and compile-time type safety.
These days, I mostly use the generic sorting syntax, but I wanted to document all three approaches for posterity.
Using sort.Interface
The oldest technique is based on sort.Interface. You create a custom type that wraps your slice and implement three methods - Len, Less, and Swap - to satisfy the interface. Then you pass this custom type to sort.Sort().
Sorting a slice of integers
The following example defines an IntSlice type. Passing an IntSlice to sort.Sort arranges its integers in ascending order:
To reverse the order, invert the comparison in the Less method and define a new type:
Just reversing the order requires you to define a separate type and implement the three methods again!
Luckily, for the basic types, the sort package provides sort.IntSlice, sort.Float64Slice, and sort.StringSlice - which already implement sort.Interface. So you don't have to do the above for sorting a slice of primitive elements. Instead, you can do this:
To reverse the order, you can use sort.Reverse as follows:
Sorting a slice of structs by age
However, if you're dealing with a slice of structs, then you do have to implement sort.Interface manually. Here, we sort by the Age field in ascending order:
We can leverage sort.Reverse to reverse the order:
Although sort.Interface can handle just about any sorting logic, you must create a new custom type (or significantly modify an existing one) each time you want to sort a different slice or the same slice in a different way. It's powerful but verbose, and can be cumbersome to maintain if you have many different sorts in your code.
Using sort.Slice
Go 1.8 introduced sort.Slice to minimize the amount of boilerplate needed for sorting. Instead of creating a new type and implementing three methods, you provide an inline comparison function that receives the two indices you're comparing.
Sorting a slice of float64
Here's a simple example that sorts floats in ascending order:
Inverting the comparison sorts them in descending order:
Sorting a slice of structs by age
For structs, the inline comparator can access struct fields:
Switching > for < will reverse the sort:
While sort.Slice is much simpler than sort.Interface, it's still not strictly type-safe: the slice parameter is defined as an interface{}, and you provide a comparator that uses indices. Go won't necessarily stop you from doing something incorrect in the comparison at compile time.
For example, this code compiles but will panic at runtime because other is referenced inside the comparator of a different slice ints, and the indices i or j can go out of bounds in other:
You won't find out you've made a mistake until runtime, when a panic occurs. There is no compiler-enforced guarantee that the func(i, j int) bool actually compares two values of the intended slice.
Note: In sort.Slice, the comparison function parameters i and j are indices. Inside the function, you must reference slice[i] and slice[j] to get the actual elements being compared.
Using generics with the slices package
Go 1.21 introduced the slices package, which provides generic sorting functions. These new functions combine the convenience of sort.Slice with the ability to detect type errors at compile time. For basic numeric or string slices that satisfy Go's "ordered" constraints, you can just call slices.Sort. For more complex or custom sorting, slices.SortFunc accepts a comparator function that returns an integer (negative if a < b, zero if they're equal, and positive if a > b).
Sorting primitive slices
When you're dealing with basic types like int, float64, or string, you can sort them immediately using slices.Sort, which arranges them in ascending order:
For descending order, you can use slices.SortFunc and invert the usual comparison:
Sorting a slice of structs by age
When dealing with more complex structures, you can define precisely how two elements should be compared:
To reverse the order, invert the numerical comparison:
Note: Unlike sort.Slice, which passes indices to the comparison function, slices.SortFunc passes the actual elements (a and b) to your comparator. Moreover, the comparator must return an int (negative, zero, or positive), rather than a boolean.
Compile-time safety
One of the major benefits of the slices package is compile-time type safety, which you don't get with sort.Sort or sort.Slice. Those older APIs use interface{} parameters or index-based comparators and don't strictly verify that your comparator operates on the right types.
As shown previously, you can accidentally reference a different slice in the comparator and your code will compile but crash at runtime. By contrast, slices.Sort and slices.SortFunc are fully generic. The compiler enforces that you pass a slice of a valid type (e.g., []int, []string, or a custom struct slice), and that your comparator's signature matches the element type. This means you get errors at compile time instead of at runtime.
For instance, if you attempt to pass an array instead of a slice:
Go will refuse to compile this code because arr is not a slice. Similarly, if your comparator for slices.SortFunc returns a type other than int, the compiler will produce an error. This helps you detect mistakes immediately, rather than discovering them in runtime.
For a practical illustration, consider sorting a slice by a case-insensitive string field:
Because your comparator expects an Animal for both a and b, you can't accidentally compare two different types or reference the wrong fields without hitting a compile-time error.
Discussion in the ATmosphere