Code that depends on binary search, ordered output, or merge-style processing often assumes a slice is already sorted. For built-in ordered values, slices.IsSorted can check that assumption. Structs and domain-specific orderings need a comparator, which is where slices.IsSortedFunc fits.
The function doesn’t rearrange anything. It answers a narrower question: does this slice already follow the order described by this comparator?
Check a struct slice by one field
slices.IsSortedFunc accepts a slice and a comparison function:
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) boolThe comparator returns a negative value when a belongs before b, a positive value when it belongs after b, and zero when the two values are equal according to the chosen ordering.
For records ordered by numeric ID:
package main
import (
"cmp"
"fmt"
"slices"
)
type User struct {
ID int
Name string
}
func main() {
users := []User{
{ID: 10, Name: "Mira"},
{ID: 20, Name: "Raka"},
{ID: 40, Name: "Sari"},
}
sorted := slices.IsSortedFunc(users, func(a, b User) int {
return cmp.Compare(a.ID, b.ID)
})
fmt.Println(sorted)
}The output is:
trueChanging the IDs to 10, 40, 20 makes the result false. The names don’t affect the check because the comparator doesn’t inspect them.
Match the check to the operation that depends on it
An order check is most useful when it validates a real precondition. slices.BinarySearchFunc, for example, requires its input to be sorted according to the comparator used for searching.
Keeping those rules aligned prevents a subtle class of bugs:
func compareUserID(a, b User) int {
return cmp.Compare(a.ID, b.ID)
}
if !slices.IsSortedFunc(users, compareUserID) {
return errors.New("users must be sorted by ID")
}If later code searches by ID, this check documents and enforces the same data contract.
Avoid validating with one key and consuming with another. A slice can be perfectly sorted by Name while being unordered by ID. Both statements can be true at the same time because sortedness only has meaning relative to an ordering rule.
Equal keys are still sorted
Adjacent values may compare equal without breaking sorted order.
users := []User{
{ID: 10, Name: "Mira"},
{ID: 20, Name: "Raka"},
{ID: 20, Name: "Sari"},
{ID: 40, Name: "Tono"},
}
sorted := slices.IsSortedFunc(users, func(a, b User) int {
return cmp.Compare(a.ID, b.ID)
})
fmt.Println(sorted) // trueThis matters when the comparator represents a non-unique key. If the application requires unique IDs, IsSortedFunc alone can’t enforce that stronger rule. A separate uniqueness check is needed.
You can also make the comparator include a tie-breaker when the desired order requires one:
func compareUser(a, b User) int {
if n := cmp.Compare(a.ID, b.ID); n != 0 {
return n
}
return cmp.Compare(a.Name, b.Name)
}Now two records with the same ID must also appear in ascending name order to satisfy the check.
Validate multi-field ordering explicitly
Composite ordering is common in logs, versions, and grouped records. The comparator should follow the same field priority used when sorting.
type Version struct {
Major int
Minor int
}
versions := []Version{
{Major: 1, Minor: 0},
{Major: 1, Minor: 4},
{Major: 2, Minor: 0},
}
sorted := slices.IsSortedFunc(versions, func(a, b Version) int {
if n := cmp.Compare(a.Major, b.Major); n != 0 {
return n
}
return cmp.Compare(a.Minor, b.Minor)
})
fmt.Println(sorted) // trueA sequence such as 1.4, 1.0, 2.0 fails because the minor values move backward inside the Major == 1 group.
One easy mistake is to sort with (Major, Minor) and validate with (Minor, Major). Each comparator can be internally consistent, but they describe different layouts. Reusing a named comparator for sorting and validation removes that mismatch:
slices.SortFunc(versions, compareVersion)
fmt.Println(slices.IsSortedFunc(versions, compareVersion))Descending order is valid too
The word “sorted” doesn’t have to mean ascending numeric order. The comparator defines the direction.
For scores stored from highest to lowest:
scores := []int{100, 90, 75, 60}
sorted := slices.IsSortedFunc(scores, func(a, b int) int {
return cmp.Compare(b, a)
})
fmt.Println(sorted) // trueReversing the arguments to cmp.Compare defines descending order. Using the ordinary cmp.Compare(a, b) comparator on that same slice would return false.
This makes IsSortedFunc useful for priority lists and rankings where descending order is intentional rather than an error.
Empty and single-element slices pass
An empty slice has no adjacent pair that violates the comparator, so it is sorted. The same applies to a slice containing one element.
var empty []User
one := []User{{ID: 10, Name: "Mira"}}
byID := func(a, b User) int {
return cmp.Compare(a.ID, b.ID)
}
fmt.Println(slices.IsSortedFunc(empty, byID)) // true
fmt.Println(slices.IsSortedFunc(one, byID)) // trueThis behavior is convenient at API boundaries because callers don’t need special branches for small inputs before validating order.
Comparator quality matters
IsSortedFunc can only judge the sequence using the comparator it receives. A comparator with inconsistent rules can make the result meaningless.
Subtraction is also a poor general-purpose integer comparator:
func(a, b int) int {
return a - b
}For fixed-width integers, subtraction can overflow. cmp.Compare(a, b) expresses the intent directly without relying on the arithmetic difference.
For strings, decide whether bytewise ordering, case folding, or another domain rule is intended, then use that same rule everywhere the slice is sorted, checked, or searched.
Check at boundaries instead of everywhere
Repeatedly checking a large slice before every operation adds work without improving correctness if the program itself owns and preserves the ordering invariant.
A better place for validation is often a boundary where ordered data enters the system: decoded input, a function receiving caller-owned data, or a test that verifies a producer’s contract.
Inside code that owns the slice, preserve order when mutating it. Insert at the correct position, re-sort after bulk changes, or keep mutation behind a small API. Then IsSortedFunc can remain a targeted assertion rather than a routine tax on every read.
Treat sortedness as a named contract
slices.IsSortedFunc is most useful when the ordering has a clear name: by ID, by timestamp descending, by version tuple, or by another domain key.
Put that rule in a comparator that sorting, validation, and search code can share where their signatures allow it. Then use the check at the boundaries that can actually violate the contract. The result is simple, but it gives ordered-slice code a concrete guard against data arriving in the wrong sequence.