A slice arrives out of order, and all you need is ascending numbers or strings. You don’t need a comparator or a wrapper type for that case. slices.Sort handles ordered element types directly and changes the existing slice into ascending order.

That directness is useful, but the in-place behavior deserves attention. Sorting a slice can also change what another slice sees when both share the same backing array. This article covers the straightforward call first, then the details that matter once slices move between functions and components.

Sort numbers with slices.Sort

For integer slices, the basic call is one line:

package main

import (
    "fmt"
    "slices"
)

func main() {
    numbers := []int{9, 2, 7, 2, -1}

    slices.Sort(numbers)

    fmt.Println(numbers)
}

The output is:

[-1 2 2 7 9]

slices.Sort accepts slices whose element type satisfies cmp.Ordered. That includes the built-in integer, floating-point, and string types, along with defined types whose underlying type is ordered.

The function doesn’t return a slice. It rearranges the elements of the slice passed to it, so assigning a result isn’t part of the API:

slices.Sort(numbers)

This is a good fit when the caller owns the slice and wants its existing contents reordered.

Sort strings in ascending order

Strings are ordered lexicographically by Go’s normal string comparison rules. No separate string-specific helper is required.

names := []string{"pear", "apple", "fig"}
slices.Sort(names)
fmt.Println(names)

Output:

[apple fig pear]

The comparison is byte-based according to Go string ordering. It isn’t locale-aware collation. If an application needs language-specific ordering, case folding, or a rule such as sorting by string length, use slices.SortFunc with an explicit comparator instead.

Case can also produce results that differ from what a person expects from a contact list. For example, uppercase and lowercase letters don’t automatically collapse into the same ordering group. Treat slices.Sort as language-level string ordering, not presentation-oriented text sorting.

slices.Sort changes the existing slice

A slice value describes a region of an underlying array. Copying the slice value doesn’t copy those elements. That matters when sorting:

numbers := []int{30, 10, 20}
alias := numbers

slices.Sort(alias)

fmt.Println(numbers) // [10 20 30]

Both variables refer to the same elements, so sorting through alias is visible through numbers.

This can be surprising at API boundaries. A helper named sortedIDs might sound as though it produces an independent result, but this implementation mutates its caller’s data:

func sortedIDs(ids []int) []int {
    slices.Sort(ids)
    return ids
}

If preserving the input matters, clone it before sorting:

func sortedIDs(ids []int) []int {
    result := slices.Clone(ids)
    slices.Sort(result)
    return result
}

Now the returned slice has separate outer storage, and rearranging it doesn’t reorder the caller’s slice. That extra allocation is a real trade-off, so don’t clone automatically when mutation is already part of the function’s contract.

Duplicates stay in the result

Sorting changes order; it doesn’t remove values. Repeated elements remain repeated:

numbers := []int{4, 1, 4, 2, 1}
slices.Sort(numbers)
fmt.Println(numbers)

Output:

[1 1 2 4 4]

If the actual requirement is a sorted set of values, sorting is only the first step. After equal values become adjacent, slices.Compact can remove adjacent duplicates:

numbers := []int{4, 1, 4, 2, 1}
slices.Sort(numbers)
numbers = slices.Compact(numbers)
fmt.Println(numbers)

Output:

[1 2 4]

Keep those operations separate when duplicates carry meaning. Sorting event codes, measurements, or vote values shouldn’t silently turn repeated observations into one value.

Floating-point NaN values come first

Floating-point sorting has one edge case worth knowing up front: slices.Sort places NaN values before other floating-point values.

values := []float64{3, math.NaN(), -1}
slices.Sort(values)

After the call, the NaN occupies the front of the slice, followed by -1 and 3.

Don’t test a NaN with equality, because a NaN doesn’t compare equal to itself. Use math.IsNaN when code needs to detect it:

if math.IsNaN(values[0]) {
    fmt.Println("first value is NaN")
}

For data pipelines, decide whether NaN belongs in the collection before relying on sorted output. Ordering NaN consistently makes the sort usable, but it doesn’t decide whether that value is valid for a business rule or calculation.

Empty and nil slices need no special branch

Calling slices.Sort on an empty slice is valid. The same is true for a nil slice:

var a []int
b := []int{}

slices.Sort(a)
slices.Sort(b)

Neither call needs a len check. This keeps sorting code simple in functions that naturally accept zero results.

A nil slice remains nil because there are no elements to rearrange and slices.Sort doesn’t replace the slice value. If nil and empty carry different meaning in serialization or an API contract, sorting by itself doesn’t erase that distinction.

Use SortFunc when the ordering rule isn’t built in

slices.Sort is intentionally narrow: it sorts ordered element types using their normal ascending order. A struct slice doesn’t have a built-in less-than relation, so this won’t compile:

// tasks := []Task{...}
// slices.Sort(tasks)

For structs, descending order, case-insensitive text, or multi-field rules, move to slices.SortFunc and state the ordering explicitly.

For example, descending integers can use cmp.Compare with reversed arguments:

slices.SortFunc(numbers, func(a, b int) int {
    return cmp.Compare(b, a)
})

Don’t reach for a custom comparator when ordinary ascending order already matches the requirement. slices.Sort(numbers) communicates that intent with less code and fewer opportunities for an inconsistent comparison function.

Don’t assume stable ordering for equal values

For plain numbers or strings, equal values are indistinguishable for many tasks. With custom records, though, preserving the original order of records that compare equally can matter.

slices.Sort isn’t an API for custom record ordering in the first place, but the distinction becomes relevant when choosing its neighboring helpers. slices.SortFunc doesn’t guarantee stable ordering. If equal elements must retain their original relative order, use slices.SortStableFunc.

A common example is sorting records by department after they have already been arranged by arrival time. If equal departments must keep that arrival sequence, stable sorting is part of the requirement, not an implementation detail.

Sort once when later operations depend on order

Sorting is often preparation for another operation. Binary search is a typical case: slices.BinarySearch expects the input to already be sorted in increasing order.

numbers := []int{8, 3, 5, 1}
slices.Sort(numbers)

index, found := slices.BinarySearch(numbers, 5)
fmt.Println(index, found)

If several searches will run against the same collection, sorting once and keeping the ordering invariant is usually clearer than repeatedly sorting before each search.

Be careful with shared mutable slices after establishing that invariant. Any later append, replacement, or swap can make the data unsorted again. When sorted order is a contract between components, document which code owns mutation rather than scattering defensive sorts across call sites.

Pick the simplest sorting helper that matches the contract

Use slices.Sort when the elements already have a natural Go ordering, ascending order is the desired result, and mutating the slice is acceptable. Clone first when the caller’s order must remain intact. Switch to slices.SortFunc for application-specific comparison rules, and use the stable variant when equal items must keep their prior relative order.

That choice keeps the code aligned with the actual requirement. For the common case of putting integers or strings in ascending order, slices.Sort is the small, direct operation it appears to be; the main thing to account for is who else can see the same underlying slice.