Sorting by Functions
Custom sorting with slices.SortFunc, multi-key sorting with cmp.Compare, and stable vs unstable sort.
Sorting by functions enables custom comparison logic - sorting structs by specific fields, multi-key sorting (primary then secondary), or business-specific orderings. slices.SortFunc accepts a less-than function; the cmp package provides comparison helpers for composing multi-key sorts.
Sort a slice of structs by a specific field using slices.SortFunc. The comparison function receives two elements and returns a negative, zero, or positive int indicating less-than, equal, or greater-than.
package main
import (
"fmt"
"slices"
)
type Person struct {
Name string
Age int
}
func main() {
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
// Sort by age (ascending)
slices.SortFunc(people, func(a, b Person) int {
return a.Age - b.Age
})
fmt.Println(people)
// [{Bob 25} {Alice 30} {Charlie 35}]
}Multi-key sort: sort by primary field, then by secondary field for ties. Use the cmp.Compare helper to chain comparisons - if the first comparison is non-zero, return it; otherwise move to the next key.
package main
import (
"cmp"
"fmt"
"slices"
)
type Task struct {
Priority int
CreatedAt int64
Title string
}
func main() {
tasks := []Task{
{1, 100, "Fix bug"},
{1, 50, "Add feature"},
{2, 75, "Review PR"},
}
// Sort by priority (ascending), then by CreatedAt (ascending) for ties
slices.SortFunc(tasks, func(a, b Task) int {
if cmp := cmp.Compare(a.Priority, b.Priority); cmp != 0 {
return cmp
}
return cmp.Compare(a.CreatedAt, b.CreatedAt)
})
for _, t := range tasks {
fmt.Printf("%d %d %s\n", t.Priority, t.CreatedAt, t.Title)
}
// 1 50 Add feature
// 1 100 Fix bug
// 2 75 Review PR
}Stable vs unstable sort: slices.Sort and slices.SortFunc are unstable (equal elements may not retain their original order). Use slices.SortStableFunc to preserve the original order of equal elements when secondary keys matter for presentation.
package main
import (
"fmt"
"slices"
)
type Item struct {
Category string
Order int
}
func main() {
items := []Item{
{"A", 1},
{"B", 2},
{"A", 3},
}
// Unstable sort by category: items within "A" may reorder
unstable := slices.Clone(items)
slices.SortFunc(unstable, func(a, b Item) int {
if a.Category < b.Category {
return -1
} else if a.Category > b.Category {
return 1
}
return 0
})
fmt.Println("Unstable:", unstable)
// Stable sort: items within "A" retain their original order
stable := slices.Clone(items)
slices.SortStableFunc(stable, func(a, b Item) int {
if a.Category < b.Category {
return -1
} else if a.Category > b.Category {
return 1
}
return 0
})
fmt.Println("Stable:", stable)
}In production
Stable sorts are critical when displaying sorted results to users - if you sort by priority then by creation time for display, an unstable sort may lose the secondary ordering, causing flickering or confusing re-orderings in UI. For backend operations (preparing data for processing), instability doesn't matter and unstable sorts are faster. Use slices.SortStableFunc when building a display payload; use slices.SortFunc for internal transformations. The cmp package helpers (cmp.Or, cmp.Compare) make multi-key sorts readable - chaining raw int comparisons with ternaries is error-prone.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free