Finding the smallest value in a Go slice doesn’t require sorting or a hand-written comparison loop. When the element type has a natural order, slices.Min states the operation directly and leaves the input untouched.

The call is compact, but its contract matters at the edges. An empty slice causes a panic, and a floating-point NaN propagates to the result. Those cases are best handled where the application can attach useful meaning to them.

Select the smallest value directly

For a non-empty integer slice, pass the slice to slices.Min:

package main

import (
    "fmt"
    "slices"
)

func main() {
    latencies := []int{18, 42, 7, 31}

    lowest := slices.Min(latencies)

    fmt.Println(lowest)
    fmt.Println(latencies)
}

The output is:

7
[18 42 7 31]

The second line confirms a useful property: selecting the minimum doesn’t reorder the slice. The function scans the values and returns the minimal element.

Strings work too because they are ordered values:

names := []string{"delta", "alpha", "omega"}
first := slices.Min(names)

fmt.Println(first) // alpha

This uses Go’s normal string ordering. If a program needs a domain-specific rule, such as selecting a struct by timestamp or cost, slices.MinFunc accepts a comparator and is a better fit.

slices.Min does not need sorted input

Sorting a collection just to take its first element does extra work and changes the collection:

slices.Sort(latencies)
lowest := latencies[0]

If the sorted sequence isn’t needed afterward, call Min on the unsorted data instead:

lowest := slices.Min(latencies)

That keeps the operation focused on selection. It also avoids an easy maintenance trap: later code may depend on the original order without realizing an earlier minimum calculation rearranged the slice.

Sorting still makes sense when the caller needs ordered output, repeated rank queries, or a sorted collection for another operation. For one minimum value, sorting isn’t required.

Guard empty input before calling slices.Min

slices.Min panics when the slice is empty. There is no neutral return value that works for every ordered type, so the function requires at least one element.

This call panics:

values := []int{}
_ = slices.Min(values)

If empty input is valid in your application, check it before calling Min. A wrapper can expose absence explicitly:

func minInt(values []int) (int, bool) {
    if len(values) == 0 {
        return 0, false
    }

    return slices.Min(values), true
}

The boolean separates “no value” from a real minimum of zero:

lowest, ok := minInt(readings)
if !ok {
    fmt.Println("no readings")
    return
}

fmt.Println(lowest)

An error return can be more appropriate at an API boundary where an empty collection represents invalid input. In a pipeline where emptiness is expected, a boolean or an earlier guard is often enough.

Account for NaN in floating-point slices

Floating-point input has a specific edge case: if any element is NaN, slices.Min returns NaN.

package main

import (
    "fmt"
    "math"
    "slices"
)

func main() {
    samples := []float64{2.5, math.NaN(), -1.4}

    lowest := slices.Min(samples)

    fmt.Println(math.IsNaN(lowest))
}

The output is:

true

That behavior preserves the presence of NaN rather than silently treating it as an ordinary numeric value. For measurement data, you may instead want to reject NaN or remove it before selecting a minimum.

A filtering pass can make that policy explicit:

valid := make([]float64, 0, len(samples))
for _, value := range samples {
    if !math.IsNaN(value) {
        valid = append(valid, value)
    }
}

if len(valid) != 0 {
    lowest := slices.Min(valid)
    fmt.Println(lowest)
}

Notice the second length check. Filtering can turn a non-empty source into an empty slice when every element is NaN.

Avoid invented starting values in manual loops

A common manual minimum loop starts from a convenient constant:

lowest := 0
for _, value := range values {
    if value < lowest {
        lowest = value
    }
}

That produces the wrong result for []int{8, 3, 12} because zero isn’t an element and is smaller than every value in the slice.

A correct manual version starts from actual data after checking the precondition:

if len(values) == 0 {
    return
}

lowest := values[0]
for _, value := range values[1:] {
    if value < lowest {
        lowest = value
    }
}

There are valid reasons to keep such a loop. You might need the index of the minimum, compute several statistics in one pass, or combine selection with other state. When none of those apply, slices.Min removes boilerplate and makes the intent visible at the call site.

Pick Min or MinFunc based on the ordering rule

slices.Min accepts slices whose element type satisfies cmp.Ordered. Integers, floating-point values, and strings are common examples.

A struct doesn’t have a built-in ordering relationship, so this isn’t valid:

type Quote struct {
    Vendor string
    Price  int
}

quotes := []Quote{
    {Vendor: "A", Price: 120},
    {Vendor: "B", Price: 95},
}

// slices.Min(quotes) does not compile.

Use slices.MinFunc when the program supplies the ordering rule:

cheapest := slices.MinFunc(quotes, func(a, b Quote) int {
    return cmp.Compare(a.Price, b.Price)
})

That distinction keeps simple ordered values simple while still supporting richer domain types through an explicit comparator.

Keep the input policy next to the call

slices.Min is a good fit when a non-empty slice already exists and its normal ordering matches the result you need. It selects the smallest element without sorting or mutating the collection.

Before the call, decide what empty input means. For floating-point data, decide how NaN should be treated. Once those boundary rules are explicit, the minimum calculation itself can stay as small as slices.Min(values).