Sorting and the slices Package

Updated

September 13, 2026

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.go

Output:

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.go

Output:

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.go

Output:

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.go

Output:

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.go

Output:

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.go

Output:

[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.SortFunc over sort.Slice on Go 1.21+.
  • Use cmp.Compare in sort functions instead of subtraction.
  • slices.Contains is linear; use a map for repeated lookups.
  • slices.BinarySearch requires a sorted slice. Sort first, search after.
  • slices.Compact removes adjacent duplicates; always sort before compacting.
  • slices.Clone when you need the original after sorting.

Try this

  1. In sort_orders.go, add a second sort by Table number ascending using slices.SortFunc and cmp.Compare.
  2. In find_tag.go, convert tags to a map[string]bool and benchmark the lookup mentally — no benchmark tool needed, just think about whether the map changes big-O.
  3. In dedup.go, skip the sort and call slices.Compact directly. Print the result. Notice that only adjacent duplicates are removed.
  4. In bsearch_id.go, add 42 to ids without re-sorting, call BinarySearch(ids, 12), and observe the wrong result. Then re-sort and try again.