Sorting and the slices Package
Sorting and the slices Package
The Go 1.21 slices package is the boring default for everything that used to need sort.Slice. slices.Sort, slices.SortFunc, slices.Contains, slices.Index, slices.Compact, and slices.Reverse cover the common desk operations without closures or ceremony. sort is still correct; slices is just shorter.
Mental model
slices.Sort sorts a []T in place when T is ordered (numbers, strings). slices.SortFunc takes a comparison function for structs. slices.Contains scans linearly — fine for ten tickets, slow for ten thousand. slices.BinarySearch requires a sorted slice and finds in O(log n).
sort.Slice(s, less) is the pre-1.21 equivalent. It still works. sort.Search is a generic binary search on indices. Both sort and slices sort in place; neither returns a new slice.
slices.Compact removes adjacent duplicates (from a sorted slice). slices.Reverse reverses in place. slices.Max / slices.Min scan for the extreme value — also require an ordered element type.
Worked examples
Case 1: Sort numbers and strings
Save as sort_tickets.go. Sort a ticket list by ID, then sort a tag list alphabetically.
// sort_tickets.go
package main
import (
"fmt"
"slices"
)
func main() {
ids := []int{12, 3, 7, 1, 9}
slices.Sort(ids)
fmt.Println("ids:", ids)
tags := []string{"vegan", "allergy", "birthday", "cold"}
slices.Sort(tags)
fmt.Println("tags:", tags)
}Run:
go run sort_tickets.goOutput:
ids: [1 3 7 9 12]
tags: [allergy birthday cold vegan]
slices.Sort modifies the original slice. There is no sorted copy returned. If you need the original order, copy the slice first: ids2 := slices.Clone(ids).
Case 2: SortFunc for structs
Save as sort_orders.go. Sort open orders by price, descending.
// sort_orders.go
package main
import (
"cmp"
"fmt"
"slices"
)
type Order struct {
ID int
Table int
Price int
}
func main() {
orders := []Order{
{ID: 7, Table: 3, Price: 22},
{ID: 8, Table: 1, Price: 8},
{ID: 9, Table: 2, Price: 15},
}
slices.SortFunc(orders, func(a, b Order) int {
return cmp.Compare(b.Price, a.Price) // descending: b before a
})
for _, o := range orders {
fmt.Printf("id=%d table=%d price=%d\n", o.ID, o.Table, o.Price)
}
}Run:
go run sort_orders.goOutput:
id=7 table=3 price=22
id=9 table=2 price=15
id=8 table=1 price=8
cmp.Compare(a, b) returns -1, 0, or 1. Swap the arguments to reverse. Do not return a.Price - b.Price — integer overflow is possible on large values; use cmp.Compare instead.
Case 3: Contains and Index
Save as find_tag.go. Check whether a tag is present; find where it sits.
// find_tag.go
package main
import (
"fmt"
"slices"
)
func main() {
tags := []string{"vegan", "birthday", "allergy"}
fmt.Println(slices.Contains(tags, "birthday"))
fmt.Println(slices.Contains(tags, "spicy"))
i := slices.Index(tags, "birthday")
fmt.Println("index of birthday:", i)
i = slices.Index(tags, "spicy")
fmt.Println("index of spicy:", i)
}Run:
go run find_tag.goOutput:
true
false
index of birthday: 1
index of spicy: -1
Index returns -1 when not found. Contains is a linear scan. If you need repeated fast lookups, use a map[string]bool.
Case 4: BinarySearch on a sorted slice
Save as bsearch_id.go. The ticket IDs are sorted. Find one by value.
// bsearch_id.go
package main
import (
"fmt"
"slices"
)
func main() {
ids := []int{1, 3, 7, 9, 12, 15}
// slices.BinarySearch returns (index, found)
i, ok := slices.BinarySearch(ids, 9)
fmt.Printf("found=%v at index=%d\n", ok, i)
i, ok = slices.BinarySearch(ids, 10)
fmt.Printf("found=%v insertion-point=%d\n", ok, i)
}Run:
go run bsearch_id.goOutput:
found=true at index=3
found=false insertion-point=4
When found is false, i is the insertion point — the index where you would insert the missing value to keep the slice sorted. Binary search requires the slice to already be sorted; calling it on an unsorted slice gives a wrong answer with no error.
Case 5: Compact and Reverse
Save as dedup.go. Remove duplicate tags after sorting; then reverse the list.
// dedup.go
package main
import (
"fmt"
"slices"
)
func main() {
tags := []string{"vegan", "birthday", "vegan", "allergy", "birthday"}
slices.Sort(tags)
fmt.Println("sorted:", tags)
tags = slices.Compact(tags)
fmt.Println("deduped:", tags)
slices.Reverse(tags)
fmt.Println("reversed:", tags)
}Run:
go run dedup.goOutput:
sorted: [allergy birthday birthday vegan vegan]
deduped: [allergy birthday vegan]
reversed: [vegan birthday allergy]
Compact only removes adjacent duplicates. Sort first, then compact. Reverse is in place; there is no ReversedClone — copy first if you want both orders.
The trap
Save as sort_trap.go. Comparing a float slice with sort.Slice and a hand-written less function that subtracted floats would quietly produce wrong order on NaN or near-equal values. Use cmp.Compare instead.
// sort_trap.go
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
prices := []float64{8.5, 3.0, 8.5, 15.0, 3.0}
// wrong: manual subtraction loses NaN safety
// slices.SortFunc(prices, func(a, b float64) int { return int(a - b) })
// right:
slices.SortFunc(prices, cmp.Compare[float64])
fmt.Println(prices)
}Run:
go run sort_trap.goOutput:
[3 3 8.5 8.5 15]
cmp.Compare handles NaN deterministically (NaN is less than everything). The subtraction trick is the classic C mistake that Go does not need.
The boring rule
- Prefer
slices.Sort/slices.SortFuncoversort.Sliceon Go 1.21+. - Use
cmp.Comparein sort functions instead of subtraction. slices.Containsis linear; use a map for repeated lookups.slices.BinarySearchrequires a sorted slice. Sort first, search after.slices.Compactremoves adjacent duplicates; always sort before compacting.slices.Clonewhen you need the original after sorting.
Try this
- In
sort_orders.go, add a second sort byTablenumber ascending usingslices.SortFuncandcmp.Compare. - In
find_tag.go, converttagsto amap[string]booland benchmark the lookup mentally — no benchmark tool needed, just think about whether the map changes big-O. - In
dedup.go, skip the sort and callslices.Compactdirectly. Print the result. Notice that only adjacent duplicates are removed. - In
bsearch_id.go, add42toidswithout re-sorting, callBinarySearch(ids, 12), and observe the wrong result. Then re-sort and try again.