The sort Package for Slices

Learn how to sort slices in Go using the sort package, including built-in type helpers, sort.Slice, sort.SliceStable, and implementing sort.Interface for custom sorting.

The sort package in the Go standard library provides everything you need to order slices. It covers sorting for built‑in types with single‑function calls, ad‑hoc sorting of any slice type with a custom comparison function, stable sorting, and a full interface for reusable sort orders. All functions sort in‑place — they modify the slice you pass, not return a new one.

The slices package alternative:

Go 1.21 introduced the slices package with generic sorting functions like slices.Sort and slices.SortFunc. This page focuses on the traditional sort package, which remains widely used and is the foundation for those newer helpers.

Sorting Built‑in Types: Ints, Float64s, Strings

For slices of int, float64, or string, the sort package provides functions that sort in increasing order with a single call. They are the shortest path to a sorted slice.

package main
import (
    "fmt"
    "sort"
)
func main() {
    ints := []int{5, 3, 4, 1, 2}
    floats := []float64{5.5, 3.3, 4.4, 1.1, 2.2}
    strings := []string{"banana", "apple", "cherry"}
    sort.Ints(ints)
    sort.Float64s(floats)
    sort.Strings(strings)
    fmt.Println("Sorted ints:", ints)
    fmt.Println("Sorted floats:", floats)
    fmt.Println("Sorted strings:", strings)
}

After sort.Ints(ints), the slice ints becomes [1 2 3 4 5]. There is no return value; the original slice is now sorted. Strings are sorted lexicographically, so "apple" comes before "banana". Internally, sort.Ints calls sort.Sort with a type sort.IntSlice that knows how to compare integers.

These helpers also have corresponding AreSorted variants: sort.IntsAreSorted, sort.Float64sAreSorted, sort.StringsAreSorted. They return a bool and are useful as pre‑conditions before binary search.

Sorting Any Slice with sort.Slice

sort.Slice lets you sort a slice of any type without implementing an interface. You pass the slice and a less function that compares elements at indices i and j.

people := []Person{
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35},
}
sort.Slice(people, func(i, j int) bool {
    return people[i].Age < people[j].Age
})
fmt.Println(people)

The less function receives two indices, so you access the slice inside the closure. This approach is quick for one‑off sorts. sort.Slice uses an unstable sort algorithm (typically quicksort), meaning equal elements may not retain their original relative order.

sort.Slice is not stable:

If you have elements that compare equal according to your less function, their original order might change. When stability matters — for example, when you want to sort by one field and preserve the order of a previous sort on another field — use sort.SliceStable instead.

The less function must be a strict weak ordering. Returning true when i == j (or when elements are equal) leads to undefined behavior and can panic. Always write the comparison so that less(i, j) && less(j, i) is never true for the same pair.

Reversing the Sort Direction with sort.Slice

To sort descending, simply flip the comparison in the less function:

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

This is the most direct way to reverse order when using sort.Slice. There is no sort.Reverse wrapper for closures; sort.Reverse works only with types that implement sort.Interface.

Stable Sorting with sort.SliceStable

sort.SliceStable has the exact same signature as sort.Slice but guarantees stability: elements that compare equal keep the order they had before sorting.

inventory := []Item{
    {"apple", 5},
    {"banana", 3},
    {"cherry", 5},
    {"date", 3},
}
sort.SliceStable(inventory, func(i, j int) bool {
    return inventory[i].Quantity < inventory[j].Quantity
})
// Result: [{banana 3} {date 3} {apple 5} {cherry 5}]

Here, banana and date both have quantity 3. Because the sort is stable, banana stays before date — their original order is preserved. Stability comes at a slightly higher cost than sort.Slice, so only reach for it when the order of equal elements matters.

Assuming stability silently breaks later:

Code that relies on the order of equal elements after a call to sort.Slice is fragile. An algorithm update or a different Go version might change the behavior. If you need stable ordering, always use sort.SliceStable explicitly.

Custom Sorting with sort.Interface

When you sort the same type multiple times or need to keep the sorting logic reusable, implement sort.Interface. It requires three methods on a named type:

  • Len() int — returns the number of elements.
  • Less(i, j int) bool — reports whether the element at index i should sort before the element at index j.
  • Swap(i, j int) — exchanges the elements at positions i and j.
type Person struct {
    Name string
    Age  int
}
type ByAge []Person
func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

With ByAge defined, you can sort a slice of Person by age:

people := []Person{
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35},
}
sort.Sort(ByAge(people))
fmt.Println(people) // [{Bob 25} {Alice 30} {Charlie 35}]

sort.Sort(ByAge(people)) converts the slice to the ByAge type, which implements sort.Interface, and then sorts it in‑place. The conversion is cheap — ByAge(people) just reinterprets the slice header without copying data.

This pattern is reusable: you can call sort.Sort(ByAge(people)) anywhere in your code. If you later need to sort by name, define another type ByName with a different Less method.

Defining a type on the slice itself works:

Because ByAge is defined as []Person, its methods operate on the same underlying array as the original slice. Modifications made by Swap are visible through the original variable people. You do not need pointer receivers here; the slice header value already points to the same data.

Using sort.Reverse with sort.Interface

To sort in descending order using sort.Interface, wrap your type with sort.Reverse:

sort.Sort(sort.Reverse(ByAge(people)))

sort.Reverse returns a new sort.Interface that inverts the Less method. It does not alter the original type. This only works when you have a concrete type implementing sort.Interface. It cannot be used directly with sort.Slice or sort.SliceStable.

Checking if a Slice is Sorted

The sort package provides sort.IsSorted to check whether a slice is ordered according to a given sort.Interface. For the built‑in helpers there are also specific functions:

ints := []int{1, 2, 3, 4, 5}
fmt.Println(sort.IntsAreSorted(ints)) // true
fmt.Println(sort.IsSorted(sort.IntSlice(ints))) // true, equivalent
people := []Person{{"Bob", 25}, {"Alice", 30}}
fmt.Println(sort.IsSorted(ByAge(people))) // false (not sorted by age)

If you sort a slice with sort.Slice, you can check it with sort.SliceIsSorted, which takes a slice and a less function identical to the one used for sorting:

sort.SliceIsSorted(people, func(i, j int) bool {
    return people[i].Age < people[j].Age
})

These checks are cheap (O(n)) and useful in tests or as guards before binary search.

Searching in Sorted Slices

The sort package includes binary search functions that operate on sorted data. They are often paired with sorting to efficiently locate elements or insertion points.

sort.SearchInts and sort.SearchFloat64s and sort.SearchStrings search for a target value in a sorted slice and return the index where it is found or where it would be inserted.

sortedInts := []int{1, 2, 4, 5}
idx := sort.SearchInts(sortedInts, 3)
fmt.Println(idx) // 2 (where 3 would be inserted)

The generic sort.Search works with any type and requires a function that reports whether the element at a given index is >= target:

idx := sort.Search(len(sortedInts), func(i int) bool {
    return sortedInts[i] >= 3
})

Binary search requires a sorted slice:

If the slice is not sorted, the result of sort.Search is unpredictable. It does not perform a linear scan; it simply runs the binary search algorithm and returns whatever index the comparisons lead to.

Common Mistakes and Misunderstandings

The straightforward API still harbors a few traps that trip up both newcomers and experienced developers.

Modifying the slice in the less function of sort.Slice. The less function should be a pure comparison. Adding elements or rearranging the slice inside it leads to chaos. The sort algorithm relies on the slice remaining stable during comparisons.

Assuming sort.Slice returns a sorted copy. The original slice is sorted in‑place. If you need to preserve the unsorted version, clone the slice first.

Using sort.Reverse with non‑interface calls. sort.Reverse requires a value that satisfies sort.Interface. It does not work with a less function or a plain slice. If you want reverse order with sort.Slice, simply swap the comparison direction in the closure.

Neglecting to define Len and Swap when implementing sort.Interface. You must provide all three methods, even though Less is the only one that determines order. If you forget one, the compiler will catch it because sort.Sort expects a full sort.Interface.

Sorting slices of structs by multiple fields incorrectly. A single less function can only produce a total order. To sort by multiple criteria (for example, by age then by name), you must compose the comparisons in the Less method:

func (a ByAgeThenName) Less(i, j int) bool {
    if a[i].Age != a[j].Age {
        return a[i].Age < a[j].Age
    }
    return a[i].Name < a[j].Name
}

Without this composition, you get only a single‑level sort.

Watch for index‑out‑of‑bounds when sorting empty slices:

Sorting an empty slice with sort.Slice or sort.Sort is safe. However, a less function that tries to access an element unconditionally (e.g., people[i].Age) will never be called for an empty slice because the algorithm never enters the loop. Still, if you manually call Less yourself on an empty slice, you will panic. Always guard manual calls with a length check.

Summary

The sort package gives you a spectrum of tools for ordering slices. For quick tasks, sort.Ints, sort.Float64s, and sort.Strings handle the common cases. When you need to sort a slice of arbitrary types once, sort.Slice is the fastest to write. When order stability matters, sort.SliceStable is the direct replacement. For reusable, multi‑sort logic, implementing sort.Interface remains the clearest and most idiomatic pattern, especially when combined with sort.Reverse for descending order.

All sorting happens in‑place, so be mindful of whether you need to preserve the original ordering. The companion search functions rely on a correctly sorted slice; always verify the order before calling sort.Search or its typed variants.

If you are starting a new project and using Go 1.21 or later, the slices package offers generic alternatives like slices.Sort and slices.SortFunc that reduce boilerplate. They build on the same sorting algorithms but present a more concise API. Still, understanding the sort package is essential — it underpins those helpers and appears in countless existing codebases.