Represent and transform data safely

Sort a Go Slice Descending with sort.Slice or sort.Interface

Sort a Go slice by age descending with sort.Slice or a reusable sort.Interface, and choose a stable sort when equal ages must retain their order.

By Niko Minadze4 min read

Editorial illustration for Sort a Go Slice Descending with sort.Slice or sort.Interface

To sort a slice by a numeric field in descending order, the shortest route is sort.Slice with a comparison that puts the larger value first. If the same ordering belongs in several places, give the slice a reusable sort.Interface implementation and reverse its ascending order. The choice is mostly between a small callback at the call site and methods you define once.

One-off descending order with sort.Slice

sort.Slice takes the slice and a comparison function. For descending ages, return whether the age at index i is greater than the age at index j:

package main

import (
    "fmt"
    "sort"
)

func main() {
    type User struct {
        Name string
        Age  int
    }

    users := []User{
        {Name: "Alice", Age: 25},
        {Name: "Bob", Age: 30},
        {Name: "Charlie", Age: 35},
    }

    sort.Slice(users, func(i, j int) bool {
        return users[i].Age > users[j].Age
    })

    fmt.Println(users)
    // [{Charlie 35} {Bob 30} {Alice 25}]
}

The comparison answers which of two elements should come first; > makes the larger age come first. The expected result runs from 35 to 25. There is no separate slice type or set of sorting methods to maintain, which makes this a good fit when the ordering is needed at just one call site. sort.Slice was introduced in Go 1.8. The descending-age example and API discussion show the same comparison pattern.

Keep the comparison close to the question the code is answering. Here it answers “older first.” If you instead need a named ordering that multiple callers can share, move that definition into a type.

Reuse an ordering with sort.Interface

A slice type implementing sort.Interface supplies three methods: Len, Swap, and Less. Define Less as ascending by age, then pass the slice through sort.Reverse when you want descending order. This example is complete on its own:

package main

import (
    "fmt"
    "sort"
)

type User struct {
    Name string
    Age  int
}

type UserSlice []User

func (u UserSlice) Len() int           { return len(u) }
func (u UserSlice) Swap(i, j int)      { u[i], u[j] = u[j], u[i] }
func (u UserSlice) Less(i, j int) bool { return u[i].Age < u[j].Age }

func main() {
    users := []User{
        {Name: "Alice", Age: 25},
        {Name: "Bob", Age: 30},
        {Name: "Charlie", Age: 35},
    }

    sort.Sort(sort.Reverse(UserSlice(users)))

    fmt.Println(users)
    // [{Charlie 35} {Bob 30} {Alice 25}]
}

UserSlice.Less puts smaller ages first. sort.Reverse inverts that ordering for the sort call, producing the same descending ages as the callback example. The wrapper is more code up front: even though age is the only field involved in the comparison, the type still needs Len and Swap. That cost is easier to justify when the ascending definition is useful in its own right, or when callers repeatedly need its reverse. For a single sort, the callback is usually clearer.

When equal ages must stay in their original order

Neither example above asks for a particular order among users with the same age. If that order matters, request a stable sort: stability preserves the original relative order of elements that compare equal. For the one-off approach, replace sort.Slice with sort.SliceStable and keep the descending comparison:

sort.SliceStable(users, func(i, j int) bool {
    return users[i].Age > users[j].Age
})

With the wrapper, use sort.Stable with the reversed ordering:

sort.Stable(sort.Reverse(UserSlice(users)))

These fragments assume the users and UserSlice definitions from the examples above. Choose a stable variant because equal-element order is a requirement, not merely because the slice happens to contain duplicate ages. Otherwise, pick the form that makes the ordering easiest to find and maintain: a local comparison for one use, or a named slice type when the ordering is reused.

Comments

No comments yet.

Leave a comment

Your comment appears after approval. Plain text only; links stay as text. Line breaks and indentation are kept.

Up to 5,000 characters. No account or email needed.

All articles

Type at least two characters.

Press <kbd>Esc</kbd> to closeOpen the search page