Ordering a Collection
Sorting means putting the elements of a collection in a certain order.
Some types have a natural order. Numbers are ordered by value: 1 comes before 2. Strings are ordered alphabetically: "four" comes before "one". For everything else, or when you want a different order, you give a custom order: a compare function that looks at two elements and says which one comes first.
Go sorts slices with the slices package:
slices.Sort()sorts a slice in natural order. It changes the slice in place: the original order is gone.slices.Sorted()(Go 1.23) returns a new sorted slice and leaves the original as it was. You pass itslices.Values(s), which walks through the elements ofs.slices.SortFunc()sorts in place using your own compare function.
The older sort.Slice() from the sort package does the same job with a "less" function that returns true or false. New code usually uses slices.
The examples below use the words one, two, three, four.
SortFunc compare function syntax
T stands for the element type. The compare function gets two elements and returns a number: negative when a comes first, positive when b comes first, and 0 when they are equal. cmp.Compare(a, b) from the cmp package gives exactly such a number for numbers and strings.
slices.SortFunc(items, func(a, b T) int {
// return a negative number if a comes first,
// a positive number if b comes first, 0 if equal
})
Natural Order
Natural order is the order a type has on its own: numbers by value, strings letter by letter.
slices.Sorted(slices.Values(s)) gives a new slice in natural order. For descending order there is no separate function: sort first, then turn the slice around with slices.Reverse().
Your own types have no natural order. In Go you add one by writing a Compare method on the struct that returns a negative number, zero or a positive number, the same rule as a compare function. Then you can pass that method to slices.SortFunc() or use it directly to compare two values.
Sorted copy
slices.Sorted() makes a new, alphabetically sorted slice. The original words slice is printed afterwards to show it did not change.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four"}
// slices.Sorted returns a new sorted slice; words stays as it was.
sorted := slices.Sorted(slices.Values(words))
fmt.Println(sorted)
fmt.Println(words)
}
Sorted descending
The copy is sorted, then slices.Reverse() flips it, so the words go from "two" down to "four".
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four"}
// Sort a copy, then turn it around.
desc := slices.Sorted(slices.Values(words))
slices.Reverse(desc)
fmt.Println(desc)
}
A type with its own order
Version has a Major and a Minor number. Its Compare method compares Major first; only when they are equal does Minor decide. cmp.Or() picks the first result that is not 0. Checking Compare(...) > 0 answers "is the first version greater?".
Output: Go program and captured output
package main
import (
"cmp"
"fmt"
)
type Version struct {
Major int
Minor int
}
// Compare returns a negative number, zero or a positive number,
// like the compare functions used by slices.SortFunc.
func (v Version) Compare(other Version) int {
return cmp.Or(
cmp.Compare(v.Major, other.Major),
cmp.Compare(v.Minor, other.Minor),
)
}
func main() {
// Is 1.2 greater than 1.3?
fmt.Println(Version{1, 2}.Compare(Version{1, 3}) > 0)
// Is 2.0 greater than 1.5?
fmt.Println(Version{2, 0}.Compare(Version{1, 5}) > 0)
}
Custom Orders
A custom order is any order you describe with a compare function.
slices.SortFunc() sorts a slice in place with that function. If two elements compare as equal, their order is not guaranteed. slices.SortStableFunc() keeps equal elements in their original order. Use it when that matters, for example when sorting words by length.
A common wish is to sort by one property of each element. Go has no special function for that: you write a compare function that compares that property, usually with cmp.Compare().
For descending order, swap a and b in the comparison: cmp.Compare(b, a) instead of cmp.Compare(a, b).
To sort by one thing and then by another, combine two comparisons with cmp.Or(). It returns the first result that is not 0. If the first comparison says the elements are equal, the second one breaks the tie.
Sort with a compare function
The compare function returns len(a) - len(b). A negative result means a is shorter and comes first, so the words end up from shortest to longest.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"aaa", "bb", "c"}
// Shorter strings come first.
slices.SortFunc(words, func(a, b string) int {
return len(a) - len(b)
})
fmt.Println(words)
}
Sort by length
The words are compared by length with cmp.Compare(len(a), len(b)). "one" and "two" are both 3 letters long; the stable sort keeps "one" before "two".
Output: Go program and captured output
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four"}
// Stable sort: words of equal length keep their original order.
slices.SortStableFunc(words, func(a, b string) int {
return cmp.Compare(len(a), len(b))
})
fmt.Println(words)
}
Sort by last letter, descending
a[len(a)-1] is the last letter of a word. The letters of b and a are passed to cmp.Compare() in swapped order, so the word with the largest last letter comes first: "four" (r), then "two" (o), then the two words ending in "e" in their original order.
Output: Go program and captured output
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four"}
// Compare the last letters, with b and a swapped for descending order.
slices.SortStableFunc(words, func(a, b string) int {
return cmp.Compare(b[len(b)-1], a[len(a)-1])
})
fmt.Println(words)
}
Sort by length, then alphabetically
cmp.Or() first compares the lengths. When two words are equally long, it falls back to strings.Compare(), which orders them alphabetically.
Output: Go program and captured output
package main
import (
"cmp"
"fmt"
"slices"
"strings"
)
func main() {
words := []string{"aaa", "bb", "c", "b", "a", "aa", "ccc"}
// cmp.Or uses the first comparison that is not 0: length first, then alphabet.
slices.SortFunc(words, func(a, b string) int {
return cmp.Or(
cmp.Compare(len(a), len(b)),
strings.Compare(a, b),
)
})
fmt.Println(words)
}
Check Sorted Order
Sometimes you only want to know whether a slice is already sorted, without sorting it.
slices.IsSorted() returns true when the slice is in ascending natural order. slices.IsSortedFunc() does the same with your own compare function. Go has no IsSortedDescending: pass a compare function with a and b swapped.
To check the order of one property, such as a user's age, compare that property inside the function.
Ascending and descending check
slices.IsSorted() confirms that 1, 2, 3, 4 are ascending. slices.IsSortedFunc() with cmp.Compare(b, a) confirms that 4, 3, 2, 1 are descending.
Output: Go program and captured output
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
ascending := []int{1, 2, 3, 4}
descending := []int{4, 3, 2, 1}
fmt.Println(slices.IsSorted(ascending))
// Swapped arguments check for descending order.
fmt.Println(slices.IsSortedFunc(descending, func(a, b int) int {
return cmp.Compare(b, a)
}))
}
Check order by a field
The users are Alice (24), Bob (31) and Charlie (29). The compare function looks only at Age. Bob is older than Charlie, so the slice is not sorted by age.
Output: Go program and captured output
package main
import (
"cmp"
"fmt"
"slices"
)
type User struct {
Name string
Age int
}
func main() {
users := []User{{"Alice", 24}, {"Bob", 31}, {"Charlie", 29}}
// Are the users in order of age?
fmt.Println(slices.IsSortedFunc(users, func(a, b User) int {
return cmp.Compare(a.Age, b.Age)
}))
}
Reverse Order
Reversing puts the last element first and the first element last.
slices.Reverse() reverses a slice in place. To get a reversed copy instead, first copy the slice with slices.Clone() and reverse the copy. The original keeps its order.
Reversed copy
The copy is reversed and printed, then the original is printed to show it is unchanged.
Output: Go program and captured output
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four"}
// Reverse a copy so the original keeps its order.
reversed := slices.Clone(words)
slices.Reverse(reversed)
fmt.Println(reversed)
fmt.Println(words)
}
Random Order
Shuffling puts the elements in a random order.
Go's math/rand package has a Shuffle() function. You give it the number of elements and a swap function; it calls the swap function for random pairs of positions. Like slices.Reverse(), it works in place, so shuffle a copy made with slices.Clone() if you still need the original.
A random source made with rand.New(rand.NewSource(seed)) gives the same "random" order every time the program runs with that seed. This is useful for tests and for examples like this one. Without a fixed seed the order changes on every run.
Shuffled copy
A random source with seed 7 shuffles a copy of the words. The swap function exchanges the elements at positions i and j.
Output: Go program and captured output
package main
import (
"fmt"
"math/rand"
"slices"
)
func main() {
words := []string{"one", "two", "three", "four"}
// A fixed seed gives the same "random" order on every run.
r := rand.New(rand.NewSource(7))
shuffled := slices.Clone(words)
r.Shuffle(len(shuffled), func(i, j int) {
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
})
fmt.Println(shuffled)
}