Standard Library Slice Utilities

Learn to use the slices and sort packages from Go's standard library to search, sort, compact, and modify slices without writing manual loops.

Go gives you slices as a dynamic, flexible way to work with sequences of data. The built‑in append and copy get you a long way, but many everyday tasks — sorting, deduplicating, searching, or safely cloning — show up often enough that the standard library offers two dedicated packages: slices (since Go 1.21) and sort. They stop you from rewriting the same loops over and over, and they handle edge cases that are easy to get wrong by hand.

Go version for slices package:

The slices package requires Go 1.21 or later. If you are pinned to an older version you can still use the sort package and write your own helpers; this page covers both worlds.

In the sections that follow you will see what each package provides, when to reach for it, and the specific mistakes that catch newcomers off guard.


The slices Package

The slices package is a collection of generic functions that operate on any slice type []E where E satisfies a minimal constraint (often comparable or cmp.Ordered). Because it uses generics, you get full type safety without casting, and the functions often read more clearly than a raw loop.

Import it with "slices". All examples on this page assume Go 1.21+.

Checking Whether an Element Exists

A loop that compares every element to a target value is trivial but verbose. The slices.Contains function answers the question directly.

package main
import (
    "fmt"
    "slices"
)
func main() {
    names := []string{"alice", "bob", "charlie"}
    fmt.Println(slices.Contains(names, "bob"))   // true
    fmt.Println(slices.Contains(names, "dave"))  // false
}

The Contains function returns a bool. It works on any slice whose element type is comparable. Internally it performs a linear scan, so it is O(n). If you need the index of the element, use slices.Index instead, which returns the first matching index or -1.

Contains only works with comparable types:

Slices of structs that contain slices or maps are not comparable. If you try slices.Contains on such a slice you will get a compile‑time error. For those cases use slices.ContainsFunc and supply your own comparison function.

Sorting a Slice with slices.Sort

slice.Sort sorts any slice whose element type is cmp.Ordered — integers, floats, strings — in ascending order. It uses a pattern‑defeating quicksort, so performance is comparable to the older sort package but the API is simpler.

numbers := []int{42, 7, 19, 101, 3}
slices.Sort(numbers)
fmt.Println(numbers) // [3 7 19 42 101]

For a slice of structs you can use slices.SortFunc and pass a comparison function. SortFunc is not stable; if you need a stable sort use slices.SortStableFunc.

type Person struct {
    Name string
    Age  int
}
people := []Person{
    {"Zara", 25},
    {"Alex", 30},
    {"Alex", 22},
}
slices.SortStableFunc(people, func(a, b Person) int {
    return cmp.Compare(a.Name, b.Name)
})
// people is now [{Alex 30} {Alex 22} {Zara 25}], Alex entries keep original order

Removing Consecutive Duplicates with Compact

slices.Compact removes adjacent duplicates from a slice, keeping the first occurrence of each run. It does not sort the slice; if the slice is not already sorted by the criterion you care about, non‑adjacent duplicates will remain.

values := []int{1, 2, 2, 3, 2, 4, 4, 4}
deduped := slices.Compact(values)
fmt.Println(deduped) // [1 2 3 2 4]

The underlying array may still contain the leftover elements beyond the new length; deduped is a slice that describes only the first few elements of the same backing array. If you want a fully independent copy, chain slices.Clone after Compact.

Compact only removes runs, not all duplicates:

If you pass [1, 2, 1] to Compact, it returns the same slice because no two identical values are next to each other. To remove all duplicates regardless of position, sort first or use a map‑based approach.

Safely Copying a Slice with Clone

Assigning one slice variable to another copies the slice header, not the underlying array. Any change made through one slice affects the other.

original := []int{1, 2, 3}
duplicate := original
duplicate[0] = 99
fmt.Println(original) // [99 2 3]

slices.Clone allocates a new backing array and copies the elements, giving you true independence:

original := []int{1, 2, 3}
copy := slices.Clone(original)
copy[0] = 99
fmt.Println(original) // [1 2 3]

This is critical when you pass slices between goroutines or want to return a snapshot of internal state without letting callers mutate it.

Modifying Slices In-Place — Replace, Delete, Insert

The package supplies several functions that modify a slice’s length and content without necessarily allocating new memory (unless capacity forces a reallocation).

  • slices.Replace(s, i, j, v...) replaces the elements in [i, j) with the elements provided in v. It returns the modified slice.
  • slices.Delete(s, i, j) removes the elements in [i, j) and returns the shortened slice.
  • slices.Insert(s, i, v...) inserts the elements v... at index i, growing the slice.
s := []string{"a", "b", "c", "d"}
s = slices.Replace(s, 1, 3, "x", "y")
fmt.Println(s) // [a x y d]
s := []int{10, 20, 30, 40, 50}
s = slices.Delete(s, 1, 3)
fmt.Println(s) // [10 40 50]

When you delete elements, the values beyond the new length are still in the underlying array but are no longer visible through the slice. If the original slice shares the array with other slices, those other slices can still see the “deleted” values.

Delete can leak memory if you keep a reference to the old array:

If the backing array contains large objects and you expect the garbage collector to free them, make sure no other slice references those elements. Assigning nil to the leftover positions (or using slices.Delete with a custom func) can help in long‑lived data structures.

s := []int{5, 10, 15}
s = slices.Insert(s, 1, 7, 8)
fmt.Println(s) // [5 7 8 10 15]

Insert always creates a new underlying array if the existing capacity is not large enough. It may also be slow if you insert repeatedly at the front of a large slice; a slice is not a linked list.

Binary Search on Sorted Slices

slice.BinarySearch works only on slices that are already sorted in ascending order. It returns the index where the value is found (if present) and a boolean indicating whether it was found. The index is useful for insertion: if the element is missing, the returned index tells you where to insert it to keep the slice sorted.

sorted := []int{1, 3, 5, 7, 9}
idx, found := slices.BinarySearch(sorted, 5)
fmt.Println(idx, found) // 2 true
idx, found = slices.BinarySearch(sorted, 6)
fmt.Println(idx, found) // 3 false  -> inserting at index 3 preserves order

Binary search is fast:

Binary search runs in O(log n) time. On large sorted slices the difference between a linear scan and a binary search is dramatic. If you find yourself calling Contains on a slice you are about to search repeatedly, sort once and use BinarySearch from then on.


The sort Package for Slices

Before the slices package existed, the sort package was the main tool for ordering and searching slices. It is still fully supported and you will encounter it in many codebases. The package provides type‑specific helpers like sort.Ints as well as the more flexible sort.Slice for custom sorting.

Sorting Built‑in Type Slices

For slices of int, float64, and string, three convenience functions sort in ascending order.

nums := []int{9, 3, 7, 1}
sort.Ints(nums)
fmt.Println(nums) // [1 3 7 9]
strs := []string{"delta", "alpha", "charlie"}
sort.Strings(strs)
fmt.Println(strs) // [alpha charlie delta]

They sort in‑place; there is no need to reassign the slice. Under the hood these functions call sort.Sort with a pre‑defined sort.Interface implementation, so they share the same algorithmic guarantees.

Sorting with a Custom Comparator (sort.Slice)

When you have a slice of structs or need a non‑standard ordering, sort.Slice is the most common tool. It takes the slice and a less(i, j int) bool function. The function must return true if the element at index i should come before the element at index j.

type Book struct {
    Title string
    Pages int
}
books := []Book{
    {"Go in Action", 300},
    {"The Go Programming Language", 400},
    {"Go Web Programming", 350},
}
sort.Slice(books, func(i, j int) bool {
    return books[i].Pages < books[j].Pages
})
fmt.Println(books)
// [{Go in Action 300} {Go Web Programming 350} {The Go Programming Language 400}]

sort.Slice is not stable. Elements that compare equal may be reordered arbitrarily. If you need the original order of equal elements preserved, use sort.SliceStable with the same arguments.

The less function must not modify the slice:

The comparator is called while the sort algorithm rearranges the elements. If you inadvertently change slice contents inside less — for example by calling another sort or modifying fields — the sort can produce incorrect results or panic. Keep the function side‑effect free.

Checking Whether a Slice Is Sorted

Before relying on a slice being sorted (for binary search, for example), you can verify it with sort.IntsAreSorted, sort.Float64sAreSorted, or sort.StringsAreSorted. For custom types, sort.SliceIsSorted takes the slice and the same less function used for sorting.

if sort.IntsAreSorted(nums) {
    fmt.Println("slice is sorted")
}

Searching in a Sorted Slice

The sort.Search function performs a binary search on a sorted slice using a user‑defined probe function. The probe receives an index and must return true when the element at that index meets the search condition.

sortedAges := []int{18, 21, 25, 30, 45}
idx := sort.Search(len(sortedAges), func(i int) bool {
    return sortedAges[i] >= 25
})
fmt.Println(idx) // 2  (index of first element >= 25)

For built‑in types there are sort.SearchInts, sort.SearchFloat64s, and sort.SearchStrings, which return the position where the value should be inserted (it does not tell you if the value is present; for that, check the returned index against the slice length and the element itself).

pos := sort.SearchInts([]int{10, 20, 30}, 20) // pos == 1, element exists
pos = sort.SearchInts([]int{10, 20, 30}, 25)  // pos == 2, 25 is missing

Choosing Between slices.Sort and sort.Slice

With Go 1.21 you have two ways to sort a slice. The table below helps you decide.

import "slices"
names := []string{"zoe", "alex", "ben"}
slices.Sort(names)

Both approaches produce identical output. The slices version is shorter for ordered types and stays purely within the slices package; the sort version remains necessary when you need the older sort.Interface for compatibility or when you are writing code that must compile with older Go.


Summary

The slices and sort packages turn repetitive slice bookkeeping into a few well‑tested function calls. slices is the modern, generic‑first toolbox for containment checks, cloning, deduplication, and in‑place modifications. sort still underpins a huge amount of existing code and remains the right choice when you need custom sorting via a comparator function without generics.