Sometimes equality isn’t enough. You may need to order version components, compare path segments, choose which byte sequence comes first, or sort records by a slice-valued key. Writing that comparison by hand is straightforward, but the prefix case and the meaning of the return value are easy places to introduce small bugs.

For slices whose element type is ordered, slices.Compare provides the standard lexicographic comparison directly. It compares elements from left to right and, when all shared elements match, treats the shorter slice as smaller.

How slices.Compare orders two slices

The function accepts two slices with the same ordered element type:

func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int

Its result has the familiar three-way comparison shape: -1 when the first slice is smaller, 0 when the slices compare equal, and 1 when the first slice is larger.

package main

import (
    "fmt"
    "slices"
)

func main() {
    left := []int{2, 4, 8}
    right := []int{2, 5, 1}

    fmt.Println(slices.Compare(left, right)) // -1
}

The comparison stops conceptually at the first unequal pair. Here the first elements are both 2, then 4 is less than 5. The trailing 8 and 1 don’t affect the result.

That is lexicographic ordering: the same basic rule used to put words in dictionary-like order. It isn’t a comparison of slice lengths, sums, maximum values, or any other aggregate property.

The first difference decides the result

Consider these two slices:

left := []int{10, 90}
right := []int{11, 0}

fmt.Println(slices.Compare(left, right)) // -1

Although left has a much larger second element, 10 is already less than 11. Once that first difference is found, later elements cannot change the ordering.

This matters when the slices encode several ordered fields. A simple version tuple is a useful example:

current := []int{1, 9, 4}
required := []int{2, 0, 0}

if slices.Compare(current, required) < 0 {
    fmt.Println("upgrade required")
}

For a deliberately simple numeric tuple, the code says exactly what it means: compare the major component first, then minor, then patch. Real software-version schemes often have additional rules for prereleases, metadata, missing components, or normalization, so don’t assume a raw integer slice implements a particular version specification.

The same caveat applies to any domain-specific ordering. slices.Compare supplies lexicographic mechanics; it doesn’t decide whether those mechanics match your business rules.

Prefixes are ordered by length

The case most worth remembering is when one slice is a prefix of the other. If every element in the shorter slice matches the beginning of the longer slice, the shorter one compares as less.

short := []int{3, 7}
long := []int{3, 7, 0}

fmt.Println(slices.Compare(short, long)) // -1
fmt.Println(slices.Compare(long, short)) // 1

The extra 0 doesn’t get compared against an imaginary zero in short. The shared prefix is equal, so length breaks the tie.

This rule is particularly useful for hierarchical keys. Suppose path components are already normalized and represented as strings:

parent := []string{"docs", "go"}
child := []string{"docs", "go", "slices"}

fmt.Println(slices.Compare(parent, child)) // -1

The parent key sorts before its extension without needing a special prefix branch.

Be careful not to turn that ordering rule into a containment rule. A negative comparison tells you which slice sorts first; it does not prove that one slice is a prefix of the other. []int{1, 8} also compares less than []int{2} even though there is no prefix relationship.

Equal contents compare equal even when nilness differs

For comparison purposes, nil and empty slices are equal:

var missing []int
empty := []int{}

fmt.Println(slices.Compare(missing, empty)) // 0

Both have zero elements, so there is no differing element and their lengths are equal. If your application assigns a distinct meaning to nil, such as “not provided” versus “provided but empty,” check that state separately before calling slices.Compare.

The same principle applies to aliases and backing arrays. Comparison is about the visible sequence of elements, not where those elements are stored. Two independently allocated slices with equal ordered values compare as 0.

Use the sign rather than depending on arithmetic

A clean way to consume slices.Compare is to test the returned sign directly:

switch result := slices.Compare(a, b); {
case result < 0:
    fmt.Println("a comes first")
case result > 0:
    fmt.Println("b comes first")
default:
    fmt.Println("same ordering value")
}

The API’s contract is already -1, 0, or 1, but treating a three-way comparator as an ordering signal keeps the surrounding code clear. In particular, don’t build unrelated arithmetic around comparator results. A comparison tells you order, not distance. A result of -1 doesn’t mean the slices differ by one unit.

This distinction becomes more useful when code later switches to another comparator or to slices.CompareFunc, where the custom comparison function’s non-zero result is propagated for the first unequal pair.

slices.Compare is not slices.Equal

If you only need a yes-or-no equality check, slices.Equal communicates that intent better:

if slices.Equal(got, want) {
    // same elements in the same order
}

Use slices.Compare when all three outcomes matter, or when a caller expects ordering. For example, a type can expose an ordering helper around a slice-valued key:

type Route struct {
    Segments []string
}

func compareRoute(a, b Route) int {
    return slices.Compare(a.Segments, b.Segments)
}

That helper can then be reused wherever the route ordering must stay consistent.

There’s another boundary: slices.Compare requires ordered element types. A slice of structs can’t be passed directly because Go has no built-in ordering for an arbitrary struct. When the domain has a meaningful ordering, use slices.CompareFunc and define that ordering explicitly instead of forcing the data into an artificial representation.

Don’t sort slices just to compare them

Sorting before a comparison changes the question. These slices contain the same integers but represent different sequences:

a := []int{1, 3, 2}
b := []int{1, 2, 3}

fmt.Println(slices.Compare(a, b)) // 1

If you sort both first, they compare equal, but you’ve stopped comparing their original order. That may be correct when the slices represent sets or multisets, but it is not lexicographic sequence comparison anymore.

It can also introduce mutation unexpectedly because the standard sorting helpers operate on the supplied slice. If your actual requirement is order-insensitive equality, state that requirement directly and choose an implementation around it rather than using sorting as an automatic prelude to Compare.

Apply slices.Compare where sequence order is the rule

slices.Compare is a good fit when a slice itself is an ordered key: components are significant from left to right, the first difference should decide the result, and a shorter equal prefix should come first. In that setting, the standard helper is shorter and less error-prone than repeating an index loop and a separate length check.

Before using it, make sure lexicographic order is genuinely the rule you want. If nil has separate meaning, handle nil explicitly. If elements need domain-specific ordering, use slices.CompareFunc. If you only care about equality, prefer slices.Equal. Choosing among those operations based on the actual question keeps comparison code small without hiding its semantics.