Sometimes you need to know whether a slice is already in the right order, not sort it again. That comes up when validating API input, checking an invariant before binary search, or avoiding unnecessary work when records are expected to arrive ordered.

For plain numbers and strings, slices.IsSorted handles the common case. When the elements are structs or the ordering is application-specific, slices.IsSortedFunc lets you define exactly what “sorted” means and returns a boolean without rearranging the slice.

What slices.IsSortedFunc checks

The function takes a slice and the same style of comparator used by slices.SortFunc:

func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool

The comparator returns a negative value when a belongs before b, zero when they compare equally, and a positive value when a belongs after b.

Consider jobs that should be ordered first by priority and then by ID:

package main

import (
    "cmp"
    "fmt"
    "slices"
)

type Job struct {
    Priority int
    ID       string
}

func compareJob(a, b Job) int {
    if n := cmp.Compare(a.Priority, b.Priority); n != 0 {
        return n
    }
    return cmp.Compare(a.ID, b.ID)
}

func main() {
    jobs := []Job{
        {Priority: 1, ID: "archive"},
        {Priority: 1, ID: "backup"},
        {Priority: 2, ID: "cleanup"},
    }

    fmt.Println(slices.IsSortedFunc(jobs, compareJob)) // true
}

Nothing in jobs changes. The function only checks whether the existing sequence agrees with the comparator.

Use slices.IsSortedFunc for struct slices

A struct has no built-in ordering in Go. Even when all its fields are individually comparable, the language can’t infer whether a Job should be ordered by priority, ID, creation time, or something else.

That makes the comparator part of the domain rule rather than boilerplate:

func compareJob(a, b Job) int {
    if n := cmp.Compare(a.Priority, b.Priority); n != 0 {
        return n
    }
    return cmp.Compare(a.ID, b.ID)
}

With that rule, this slice is not sorted:

jobs := []Job{
    {Priority: 1, ID: "backup"},
    {Priority: 1, ID: "archive"},
    {Priority: 2, ID: "cleanup"},
}

fmt.Println(slices.IsSortedFunc(jobs, compareJob)) // false

The priorities don’t reveal the problem because the first two values tie. The secondary ID comparison does: backup comes after archive under the declared ordering, so their current positions are reversed.

This is also why a validation comparator should match the comparator used elsewhere. If one path sorts by priority alone while another validates by priority and ID, the same data can be considered sorted by one path and unsorted by the other.

Check before operations that require sorted input

Binary search is a practical reason to validate ordering. slices.BinarySearchFunc expects its input to be sorted according to the same ordering used by its comparison logic. If that precondition isn’t guaranteed by the caller, checking it can turn a subtle wrong-result bug into an explicit error.

func validateJobs(jobs []Job) error {
    if !slices.IsSortedFunc(jobs, compareJob) {
        return fmt.Errorf("jobs must be ordered by priority and ID")
    }
    return nil
}

Don’t automatically add this check before every binary search. A sortedness check scans the slice, so repeatedly validating a collection whose invariant is already guaranteed adds work without improving correctness. It fits best at trust boundaries: after decoding external input, when accepting caller-owned data, or where an invariant is otherwise difficult to establish.

If your code owns the slice and needs it sorted regardless of its current state, calling slices.SortFunc directly is usually clearer than checking and then sorting.

Descending order is still a valid sorted order

The word “ascending” in the API is relative to the comparator. Reverse the comparator and IsSortedFunc can validate descending data naturally.

jobs := []Job{
    {Priority: 3, ID: "cleanup"},
    {Priority: 2, ID: "backup"},
    {Priority: 1, ID: "archive"},
}

isDescending := slices.IsSortedFunc(jobs, func(a, b Job) int {
    return cmp.Compare(b.Priority, a.Priority)
})

fmt.Println(isDescending) // true

This is easier to reason about when the comparator itself has a descriptive name. If descending priority is a recurring rule, a compareJobPriorityDescending function is less error-prone than reversing arguments independently at several call sites.

Equal neighbors are allowed

Sorted doesn’t mean every adjacent element must be different. A comparator result of zero says the two values occupy the same position in the ordering, so repeated or equivalent values can still form a sorted slice.

For example, a comparator that looks only at priority considers these jobs sorted:

jobs := []Job{
    {Priority: 1, ID: "backup"},
    {Priority: 1, ID: "archive"},
    {Priority: 2, ID: "cleanup"},
}

byPriority := func(a, b Job) int {
    return cmp.Compare(a.Priority, b.Priority)
}

fmt.Println(slices.IsSortedFunc(jobs, byPriority)) // true

If ID order matters inside each priority, include it in the comparator. IsSortedFunc can only enforce the ordering you describe.

This distinction is useful for grouped data. A slice can be correctly grouped by account ID or priority even if records inside each group remain in arrival order. Don’t add tie-breakers unless the application actually requires them.

Empty and one-element slices are sorted

There is no adjacent pair that can violate the ordering in an empty or one-element slice, so both are reported as sorted. A nil slice is empty and behaves the same way.

var nilJobs []Job
emptyJobs := []Job{}
oneJob := []Job{{Priority: 1, ID: "archive"}}

fmt.Println(slices.IsSortedFunc(nilJobs, compareJob))   // true
fmt.Println(slices.IsSortedFunc(emptyJobs, compareJob)) // true
fmt.Println(slices.IsSortedFunc(oneJob, compareJob))    // true

That behavior is convenient for generic validation, but it may not match a business rule that requires at least one item. Sortedness and non-emptiness are separate properties. Check len(jobs) explicitly when empty input is invalid.

Keep the comparator consistent

A comparator isn’t just a callback that happens to return negative and positive numbers. It defines the ordering that IsSortedFunc is checking. Contradictory results can make the answer meaningless.

Prefer comparison helpers such as cmp.Compare over subtraction:

return cmp.Compare(a.Priority, b.Priority)

A shortcut such as a.Priority - b.Priority can overflow for integer values near the type’s limits and produce the wrong sign.

Also avoid comparators whose result changes during the check because they read mutable external state. If ordering depends on configuration, capture a stable value before calling IsSortedFunc. The same pair of elements should not switch order halfway through validation.

Validate the invariant where it enters your code

slices.IsSortedFunc is most useful when sorted order is a property you expect rather than an operation you want to perform. Put the comparator next to the domain rule, reuse it for sorting and validation, and check the invariant at the boundary where untrusted or caller-owned data enters the system.

If the data simply needs to become ordered, sort it. If it must already be ordered and a violation should be visible, slices.IsSortedFunc expresses that requirement directly without modifying the input.