Sorting

Sorting numbers or strings in their natural order is one call: slices.Sort. Real data needs more — products by price, people by surname then first name, newest first. This lesson shows how to sort with a comparison function using slices.SortFunc and the cmp package, how to sort by several keys, stable sorting, and a quick look at the older sort package you'll still meet in existing code.

Natural order

GO
package main

import (
	"fmt"
	"slices"
)

func main() {
	nums := []int{42, 7, 19, 3}
	slices.Sort(nums)
	fmt.Println(nums)

	words := []string{"pear", "Apple", "fig", "banana"}
	slices.Sort(words)
	fmt.Println(words)
}
Output
[3 7 19 42]
[Apple banana fig pear]

slices.Sort sorts in place, smallest first, and works for any ordered type: integers, floats and strings. Strings sort by bytes, so uppercase letters come before lowercase.

Custom order with SortFunc

slices.SortFunc takes a comparison function cmp(a, b) that returns a negative number if a should come before b, a positive number if after, and zero if they're equal. cmp.Compare produces exactly that for any two ordered values:

GO
package main

import (
	"cmp"
	"fmt"
	"slices"
	"strings"
)

type Product struct {
	Name  string
	Price float64
}

func main() {
	products := []Product{
		{"Lamp", 34.5}, {"Desk", 249}, {"Chair", 129}, {"Mug", 8.9},
	}

	slices.SortFunc(products, func(a, b Product) int {
		return cmp.Compare(a.Price, b.Price)
	})
	fmt.Println(products)

	slices.SortFunc(products, func(a, b Product) int {
		return cmp.Compare(b.Price, a.Price) // swapped: most expensive first
	})
	fmt.Println(products[0].Name)

	words := []string{"pear", "Apple", "fig", "banana"}
	slices.SortFunc(words, func(a, b string) int {
		return strings.Compare(strings.ToLower(a), strings.ToLower(b))
	})
	fmt.Println(words)
}
Output
[{Mug 8.9} {Lamp 34.5} {Chair 129} {Desk 249}]
Desk
[Apple banana fig pear]

To reverse an order, swap a and b in the comparison. For case-insensitive sorting, compare lowercased strings.

Sorting by several keys

cmp.Or returns the first of its arguments that isn't zero, which makes multi-key comparisons read like a list of tie-breakers:

GO
package main

import (
	"cmp"
	"fmt"
	"slices"
)

type Person struct {
	Last, First string
	Age         int
}

func main() {
	people := []Person{
		{"Silva", "Rui", 41},
		{"Costa", "Ana", 29},
		{"Silva", "Ana", 35},
		{"Costa", "Ana", 22},
	}

	slices.SortFunc(people, func(a, b Person) int {
		return cmp.Or(
			cmp.Compare(a.Last, b.Last),   // by surname,
			cmp.Compare(a.First, b.First), // then first name,
			cmp.Compare(a.Age, b.Age),     // then age
		)
	})
	for _, p := range people {
		fmt.Println(p.Last, p.First, p.Age)
	}
}
Output
Costa Ana 22
Costa Ana 29
Silva Ana 35
Silva Rui 41

Stable sorting

SortFunc doesn't promise to keep equal elements in their original order. When that order matters — for instance, you sorted by date earlier and now want to group by category without losing the date order — use slices.SortStableFunc:

GO
package main

import (
	"cmp"
	"fmt"
	"slices"
)

type Task struct {
	Title    string
	Priority int
}

func main() {
	tasks := []Task{ // already in the order they were added
		{"write report", 2}, {"fix bug", 1}, {"email team", 2}, {"deploy", 1}, {"lunch", 3},
	}
	slices.SortStableFunc(tasks, func(a, b Task) int {
		return cmp.Compare(a.Priority, b.Priority)
	})
	for _, t := range tasks {
		fmt.Println(t.Priority, t.Title)
	}
}
Output
1 fix bug
1 deploy
2 write report
2 email team
3 lunch

Within each priority, tasks keep the order they were added in.

Sorting map entries

Maps have no order, so to show a map sorted by value, collect the keys and sort them by their values:

GO
package main

import (
	"cmp"
	"fmt"
	"maps"
	"slices"
)

func main() {
	sales := map[string]int{"north": 120, "south": 340, "east": 95, "west": 210}

	regions := slices.SortedFunc(maps.Keys(sales), func(a, b string) int {
		return cmp.Compare(sales[b], sales[a]) // highest sales first
	})
	for i, r := range regions {
		fmt.Printf("%d. %-5s %d\n", i+1, r, sales[r])
	}
}
Output
1. south 340
2. west  210
3. north 120
4. east  95

Searching sorted data

Once data is sorted, slices.BinarySearch finds an element in a handful of steps even among millions. It also returns the position where a missing value would go, which is how you insert while keeping the slice sorted:

GO
package main

import (
	"fmt"
	"slices"
)

func main() {
	ids := []int{3, 8, 15, 23, 42}
	i, found := slices.BinarySearch(ids, 23)
	fmt.Println(i, found)

	i, found = slices.BinarySearch(ids, 10)
	fmt.Println(i, found)
	ids = slices.Insert(ids, i, 10)
	fmt.Println(ids)
}
Output
3 true
2 false
[3 8 10 15 23 42]

The older sort package

Code written before Go 1.21 uses the sort package, whose sort.Slice takes a "less" function working on indexes. It still works, but the slices functions are faster, type-safe and easier to read, so prefer them in new code:

GO
package main

import (
	"fmt"
	"sort"
)

func main() {
	ages := []int{41, 29, 35}
	sort.Slice(ages, func(i, j int) bool { return ages[i] < ages[j] })
	fmt.Println(ages)

	names := []string{"cleo", "ana", "ben"}
	sort.Strings(names) // older equivalent of slices.Sort
	fmt.Println(names)
}
Output
[29 35 41]
[ana ben cleo]

Try it yourself

Sort the films by rating (highest first), and alphabetically by title when ratings tie.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
8.5 Alien
8.3 Heat
8.3 Up
7.9 Brazil
7.2 Cars
Next: writing functions.