Finding the smallest number in a slice is straightforward, but real programs often need the cheapest quote, earliest task, shortest route, or lowest-priority struct. Those values aren’t ordered by Go itself, so slices.Min can’t express the rule.

slices.MinFunc handles that case. You provide the slice and a comparator; it returns the element that is minimal according to that comparator.

What slices.MinFunc does

The function accepts slices of any element type:

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

The comparator follows the same three-way convention used by other generic slice helpers: return a negative value when a should come before b, zero when they compare equally, and a positive value when a should come after b.

Suppose a scheduler stores jobs as structs and wants the job with the shortest delay:

package main

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

type Job struct {
    Name  string
    Delay int
}

func main() {
    jobs := []Job{
        {Name: "backup", Delay: 30},
        {Name: "email", Delay: 5},
        {Name: "cleanup", Delay: 12},
    }

    fastest := slices.MinFunc(jobs, func(a, b Job) int {
        return cmp.Compare(a.Delay, b.Delay)
    })

    fmt.Println(fastest.Name) // email
}

Nothing is sorted or rearranged. MinFunc scans the slice and returns the minimum element it finds under the supplied ordering.

Use slices.MinFunc when the element itself isn’t ordered

For a slice of integers or strings, slices.Min is usually enough:

smallest := slices.Min([]int{12, 5, 30})

A struct doesn’t have Go’s built-in ordering operators, and even when a type contains ordered fields, the program still has to decide which field defines “minimum.” MinFunc puts that domain rule in the comparator.

For example, the cheapest offer can be selected without creating a second slice of prices:

type Offer struct {
    Vendor string
    Price  int
}

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

That distinction matters when the caller needs the whole Offer, not merely its minimum Price. The result still contains the vendor and any other fields associated with the selected value.

Ties return the first minimum element

When several elements compare equally at the minimum, slices.MinFunc returns the first one.

jobs := []Job{
    {Name: "email", Delay: 5},
    {Name: "backup", Delay: 30},
    {Name: "sync", Delay: 5},
}

fastest := slices.MinFunc(jobs, func(a, b Job) int {
    return cmp.Compare(a.Delay, b.Delay)
})

fmt.Println(fastest.Name) // email

Both email and sync have the same minimum delay, but email appears first. This is useful when input order already represents a meaningful preference, such as configuration order.

Don’t rely on that tie behavior when the input order is incidental. If two values need deterministic ordering by their own fields, encode the tie-breaker in the comparator instead.

Add tie-breakers directly to the comparator

A comparator can order by more than one field. Compare the primary field first, then compare a secondary field only when the first comparison is equal:

best := slices.MinFunc(jobs, func(a, b Job) int {
    if n := cmp.Compare(a.Delay, b.Delay); n != 0 {
        return n
    }
    return cmp.Compare(a.Name, b.Name)
})

Now jobs with the same delay are ordered by name. The minimum no longer depends on which tied job happened to occur first.

This pattern is preferable to sorting the entire slice just to inspect element zero. If the only requirement is one minimum value, sorting performs more work and mutates the order unless you make a copy first. Sorting makes sense when later code also needs every element in order; MinFunc fits the narrower selection problem.

Keep the comparator consistent

A custom comparator is part of the operation’s correctness. It should describe a coherent ordering rather than changing its answer based on mutable external state.

For numeric fields, cmp.Compare is clearer than subtraction:

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

Avoid this shortcut:

return a.Delay - b.Delay

Subtraction can overflow for integer values near the type’s limits, which can produce the wrong sign and therefore the wrong ordering. A comparison helper states the intent without depending on arithmetic staying in range.

Comparators that read a map or configuration which changes during the call are also difficult to reason about. Compute any stable comparison inputs first, then let the comparator consistently compare two elements under the same rule.

Empty slices panic

slices.MinFunc requires at least one element. Calling it with an empty or nil slice panics:

var jobs []Job

fastest := slices.MinFunc(jobs, func(a, b Job) int {
    return cmp.Compare(a.Delay, b.Delay)
}) // panic

That contract is reasonable when an empty input means the caller has violated an invariant. If an empty slice is normal application state, check it before calling MinFunc and represent the absence explicitly:

func fastestJob(jobs []Job) (Job, bool) {
    if len(jobs) == 0 {
        return Job{}, false
    }

    job := slices.MinFunc(jobs, func(a, b Job) int {
        return cmp.Compare(a.Delay, b.Delay)
    })
    return job, true
}

Returning a boolean keeps the zero value of Job from being confused with an actual minimum. A pointer or application-specific option type can serve the same purpose if that better matches the surrounding API.

MinFunc returns a value, not a position

The result is the selected element value. MinFunc doesn’t return its index, so it’s not the best fit when the next operation needs to modify the original slice entry in place.

You can search for a minimum index with a small loop instead:

if len(jobs) == 0 {
    // handle empty input
}

minIndex := 0
for i := 1; i < len(jobs); i++ {
    if jobs[i].Delay < jobs[minIndex].Delay {
        minIndex = i
    }
}

jobs[minIndex].Delay = 0

Trying to recover the position after calling MinFunc can become ambiguous when duplicate values exist. Choose the operation based on what the caller actually needs: MinFunc for the minimum value, an index-tracking loop when position matters.

Select one minimum without sorting the slice

Use slices.MinFunc when you need one minimum element from a slice whose ordering depends on application logic. Put the rule in a small, stable comparator, add explicit tie-breakers when input order shouldn’t decide ties, and guard empty input when emptiness is a valid state.

If the rest of the program also needs the full sequence ordered, sort it instead. When all you need is the cheapest offer, shortest delay, or earliest custom value, selecting that value directly keeps the code focused on the actual job.