Represent and transform data safely

Find the Maximum in a Go Slice Without Sorting It

Find the maximum in a Go slice without sorting it. Use a loop for an explicit empty-input result or slices.Max for non-empty ordered values.

By Niko Minadze3 min read

Editorial illustration for Find the Maximum in a Go Slice Without Sorting It

If you need only the largest value in a slice, leave the slice in its original order. A loop can track the maximum as it reads each element. For a non-empty slice of ordered values, slices.Max is a shorter option. The decision to make first is what your function should do when there is no value to return.

Give empty input its own result

An int function that returns 0 for an empty slice leaves callers with an ambiguity: was the slice empty, or was its maximum actually zero? Return (value, ok) when an empty slice is an expected possibility. Set ok to false for empty input; callers should inspect it before using value.

If empty input instead means the caller has made a mistake, an (int, error) return is a clearer contract. That lets the function explain why it could not produce a maximum. Choose the contract at the function boundary rather than making every caller guess what a zero means.

Find the maximum in one pass

Check for an empty slice before reading its first element. Then initialize the maximum from that element—not from zero—and compare each remaining value against it:

package main

import "fmt"

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

    max := values[0]
    for _, value := range values[1:] {
        if value > max {
            max = value
        }
    }
    return max, true
}

func main() {
    for _, values := range [][]int{
        {},
        {7},
        {-9, -3, -14},
        {0, 6, 2},
    } {
        value, ok := maxInt(values)
        fmt.Printf("%v: value=%d ok=%t\n", values, value, ok)
    }
}

For the empty slice, the returned value is 0, but ok is false: that zero is a placeholder, not a maximum. The singleton returns 7. For {-9, -3, -14}, the result is -3, because it is greater than the other two values. Initializing max to zero would get that case wrong. The loop neither sorts nor modifies the input; it keeps just the best value seen so far.

This helper is a good fit when its empty-input policy is part of an API used by several callers. It also makes that policy visible in the return type. At a call site, handle both results explicitly:

value, ok := maxInt(values)
if !ok {
    // Handle the absence of a maximum here.
} else {
    // Use value here.
}

The ok result matters even if most calls have values. A caller that ignores it can still mistake the placeholder zero for a maximum. Conversely, when ok is true, zero is an ordinary possible result; the flag reports whether a maximum was found, not whether that maximum is nonzero.

Use slices.Max for non-empty ordered slices

The standard library’s slices.Max accepts a slice whose element type satisfies cmp.Ordered and returns an element of that type. If your code must represent absence explicitly, check the slice before calling it:

package main

import (
    "fmt"
    "slices"
)

func main() {
    values := []int{-9, -3, -14}
    if len(values) == 0 {
        fmt.Println("no maximum")
        return
    }

    fmt.Println(slices.Max(values)) // -3
}

For this non-empty slice, slices.Max and maxInt select the same value, -3. Use slices.Max when the caller already knows the slice is non-empty or can handle that check nearby. Keep the loop helper when you want one function to own the (value, ok) contract. Neither approach needs to rearrange the slice to find its largest value.

Comments

No comments yet.

Leave a comment

Your comment appears after approval. Plain text only; links stay as text. Line breaks and indentation are kept.

Up to 5,000 characters. No account or email needed.

All articles

Type at least two characters.

Press <kbd>Esc</kbd> to closeOpen the search page