How to Use maps and slices Packages from Go Standard Library

Use the maps and slices packages in Go 1.21+ to easily sort, copy, and manipulate maps and slices with built-in utility functions.

When loops get in the way

You have a map of configuration values. You need to print them in alphabetical order. You reach for a loop, extract keys, sort them, and realize you've written this boilerplate three times this week. Or you need to check if a slice contains a specific ID before processing it. You write a for loop, check equality, break, and set a flag. Go 1.21 introduced maps and slices packages to stop you from reinventing these wheels.

Generic utilities for collections

Maps and slices are the workhorses of Go data structures. Before Go 1.21, the standard library gave you the types but left the common operations to you. You had to write sort.Slice with a custom comparator or loop through keys manually. The maps and slices packages fill that gap. They provide generic functions that work on any map or slice type. Think of them as the Swiss Army knife attached to your data structures. You get sorting, searching, copying, and merging without importing third-party libraries or writing repetitive code. These packages use Go generics, so the compiler generates type-safe code for your specific types. Maps and slices are generic. The functions are too.

Minimal example

Here's the simplest usage: sort a slice of integers and extract sorted keys from a map.

package main

import (
	"fmt"
	"maps"
	"slices"
)

func main() {
	// slices.Sort modifies the slice in place.
	// It works on any type that implements cmp.Ordered.
	nums := []int{3, 1, 4, 1, 5}
	slices.Sort(nums)
	fmt.Println(nums) // [1 1 3 4 5]

	// maps.Keys returns a new slice containing all keys.
	// Map iteration order is random, so keys are unsorted.
	m := map[string]int{"b": 2, "a": 1}
	keys := maps.Keys(m)

	// Sort the keys to get deterministic output.
	slices.Sort(keys)
	fmt.Println(keys) // [a b]
}

The maps and slices packages are part of the standard library. You don't need external dependencies for common collection operations. gofmt will sort these imports alphabetically alongside other standard packages. Trust the tool. Let it format your code.

How it works under the hood

At compile time, the generics in slices.Sort check that your element type implements the cmp.Ordered interface. This interface covers basic types like int, float64, and string. If you try to sort a slice of structs without a custom function, the compiler rejects the program with type User does not implement cmp.Ordered. You must use slices.SortFunc instead. At runtime, slices.Sort uses a fast sorting algorithm optimized for Go's memory layout. It sorts the backing array in place. maps.Keys iterates over the map's internal hash table and appends keys to a new slice. Since map iteration order is randomized by design, the resulting slice has random order until you sort it. The compiler checks types. The runtime moves memory.

Realistic patterns

Real code often needs to filter data, merge configurations, or sort complex structures. Here's how to handle these scenarios without writing loops.

Sorting structs requires a custom comparison function. Here's how to sort users by ID using slices.SortFunc.

package main

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

type User struct {
	ID   int
	Name string
}

func main() {
	users := []User{
		{ID: 2, Name: "Bob"},
		{ID: 1, Name: "Alice"},
	}

	// SortFunc takes a comparison function.
	// cmp.Compare returns -1, 0, or 1.
	slices.SortFunc(users, func(a, b User) int {
		return cmp.Compare(a.ID, b.ID)
	})

	fmt.Println(users) // [{1 Alice} {2 Bob}]
}

Filtering a slice is common when processing lists. Here's how to remove inactive users safely.

package main

import (
	"fmt"
	"slices"
)

type User struct {
	ID     int
	Name   string
	Active bool
}

func main() {
	users := []User{
		{ID: 1, Name: "Alice", Active: true},
		{ID: 2, Name: "Bob", Active: false},
		{ID: 3, Name: "Charlie", Active: true},
	}

	// Clone creates a new slice with copied elements.
	// This isolates the result from the original data.
	activeUsers := slices.Clone(users)

	// DeleteFunc removes elements where the function returns true.
	// We remove users who are not active.
	slices.DeleteFunc(activeUsers, func(u User) bool {
		return !u.Active
	})

	fmt.Println(activeUsers) // [{1 Alice true} {3 Charlie true}]
}

Merging maps is essential for configuration systems. Here's how to combine base settings with overrides.

package main

import (
	"fmt"
	"maps"
)

func main() {
	base := map[string]string{"theme": "dark", "lang": "en"}
	override := map[string]string{"lang": "fr", "beta": "true"}

	// Clone creates a shallow copy of the map.
	// Changes to result won't affect base.
	result := maps.Clone(base)

	// Copy writes key-value pairs from override into result.
	// Keys in override overwrite existing keys in result.
	maps.Copy(result, override)

	fmt.Println(result) // map[beta:true lang:fr theme:dark]
}

When you clone a slice or map, you get a shallow copy. If your elements are pointers or structs containing pointers, the clone shares the underlying data. Be careful mutating nested structures. The clone copies the references, not the pointed-to values.

Pitfalls and errors

Sorting structs requires a custom function. If you pass a slice of structs to slices.Sort, the compiler rejects this with type User does not implement cmp.Ordered. You must use slices.SortFunc and provide a comparison function.

maps.Copy direction matters. maps.Copy(dst, src) writes to dst. It's easy to swap arguments. The compiler won't catch this if types match, but the logic breaks. Always read the signature: destination first, source second.

maps.Keys allocates memory. It creates a new slice. If the map is huge, this is a memory spike. Consider iterating with range if you don't need the keys as a slice.

slices.Delete takes indices, while slices.DeleteFunc takes a predicate. Use Delete if you know the indices to avoid iterating the whole slice. DeleteFunc scans the slice and shifts elements, which is O(n). If you are deleting many items, consider filtering into a new slice instead.

If your comparison function in SortFunc is inconsistent, the sort panics at runtime. For example, if a < b and b < a both return true, the algorithm cannot determine order. The program crashes with invalid memory address or nil pointer dereference or a sort panic. Ensure your comparison logic is transitive and consistent.

Cloning is cheap for small data. Measure allocations for large maps.

When to use what

Use slices.Sort when you have a slice of ordered types like integers or strings and need ascending order. Use slices.SortFunc when you need to sort structs or use a custom comparison logic. Use maps.Clone when you need a mutable copy of a map without affecting the original. Use maps.Copy when you want to merge one map into another, overwriting duplicates. Use slices.Contains when you need a quick check for an exact value in a slice. Use slices.BinarySearch when you have a sorted slice and need fast lookup. Use a manual loop when you need to perform multiple operations during iteration that generics can't express. Use range over a map directly when you don't need sorted keys and just need to process all entries. Standard library functions are fast and safe. Write loops only when you need control.

Where to go next