How to Use the cmp Package for Comparison Functions

Use `cmp.Compare` to compare two values of the same type, returning -1, 0, or 1 to indicate less than, equal, or greater than. This function replaces manual comparison logic for any comparable type.

The comparison problem

You are building a configuration loader that reads two JSON files and needs to detect changes. You write a helper function to compare slices of strings. It works. A week later you need to sort a list of tasks by priority, then by creation timestamp. You write another comparison function. Then you need to find the maximum value in a slice of custom structs. You realize you are rewriting the same ordering logic across three different files. Before Go 1.21, this was the standard workflow. The language left comparison contracts to the developer or scattered them across packages like sort and strings.

Go 1.21 introduced the cmp package to standardize how values are ordered. The package exports a small set of functions that follow a single, predictable contract. You stop writing if a < b { return -1 } else if a > b { return 1 } else { return 0 } manually. You hand the values to cmp.Compare and get back a consistent signal. The standard library now relies on this contract for sorting, searching, and min/max operations.

Comparison functions are plumbing. Write them once, reuse them everywhere.

How cmp.Compare works

The cmp.Compare function accepts two values of the same type and returns an integer. The integer follows a strict triple-result rule. A negative value means the first argument comes before the second. Zero means they are equal. A positive value means the first argument comes after the second. The exact magnitude does not matter. The standard library only checks the sign.

This contract replaces manual comparison logic for any comparable type. Integers, floats, strings, arrays, and slices all work out of the box. The function handles the element-by-element comparison for compound types. If you pass two slices, Go compares the first element. If they match, it moves to the second. It stops at the first difference. If one slice is a prefix of the other, the shorter slice is considered less.

Here is the simplest usage: spawn two slices, compare them, and observe the result.

package main

import (
	"fmt"
	"cmp"
)

func main() {
	// Two slices with identical prefixes but different tails
	a := []int{1, 2, 3}
	b := []int{1, 2, 4}

	// cmp.Compare returns -1, 0, or 1 based on ordering
	result := cmp.Compare(a, b)
	fmt.Println(result)
}
# output:
-1

The function returns -1 because a is lexicographically less than b. The comparison stops at index two where 3 is less than 4. You do not need to write a loop. You do not need to check lengths manually. The package handles the traversal and returns the sign.

The triple-result contract is the foundation of Go's generic sorting. Every function in the slices package that accepts a comparator expects this exact signature. You pass cmp.Compare directly to slices.SortFunc or slices.BinarySearchFunc and the standard library handles the rest.

Comparison is a contract, not a calculation. Trust the sign, ignore the magnitude.

Walking through the comparison

When the compiler sees cmp.Compare(a, b), it first verifies that the type of a and b is comparable. Go's type system enforces this at compile time. If you pass a map, a function, or a slice containing a map, the program fails to build. The compiler rejects the code with invalid operation: cannot compare x with y (operator not defined on map[string]int). This early failure prevents subtle runtime panics.

At runtime, the function executes a straightforward algorithm. For primitive types like int or string, it delegates to the built-in <, ==, and > operators. For arrays and slices, it iterates from index zero upward. Each iteration compares the elements at the current position. If the elements differ, the function returns the sign of that difference immediately. If the loop finishes without finding a difference, the function compares the lengths. The shorter sequence is less. If lengths match, the function returns zero.

This behavior matches lexicographical ordering. It is predictable and consistent across all comparable types. You can chain comparisons for multi-field sorting. If the primary field matches, you fall back to the secondary field. The standard library does not provide a built-in chain function, so you write a small wrapper that returns early on non-zero results.

Here is a realistic example that sorts a slice of structs by two fields.

package main

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

// Task represents a unit of work with priority and name
type Task struct {
	Priority int
	Name     string
}

func main() {
	// Unsorted slice of tasks
	tasks := []Task{
		{Priority: 2, Name: "deploy"},
		{Priority: 1, Name: "test"},
		{Priority: 2, Name: "build"},
		{Priority: 1, Name: "lint"},
	}

	// Sort by priority first, then alphabetically by name
	slices.SortFunc(tasks, func(a, b Task) int {
		// Compare primary field first
		if res := cmp.Compare(a.Priority, b.Priority); res != 0 {
			return res
		}
		// Fall back to secondary field on tie
		return cmp.Compare(a.Name, b.Name)
	})

	// Print the ordered slice
	for _, t := range tasks {
		fmt.Printf("Priority: %d, Name: %s\n", t.Priority, t.Name)
	}
}
# output:
Priority: 1, Name: lint
Priority: 1, Name: test
Priority: 2, Name: build
Priority: 2, Name: deploy

The comparator returns early when the priority differs. If priorities match, it compares the names. The slices.SortFunc function uses this comparator to rearrange the slice in place. The algorithm is stable for equal elements, meaning items that compare as zero retain their original relative order.

Go favors explicit control flow over hidden magic. Write the fallback chain yourself. Keep the comparator focused on a single ordering rule.

Handling edge cases and compiler checks

Floating point numbers introduce a well-known edge case. The IEEE 754 standard defines NaN (not a number) as unequal to everything, including itself. This breaks the total ordering requirement that sorting algorithms expect. If you pass NaN to cmp.Compare, the function treats it as greater than any valid number. This keeps the sort stable and prevents panics, but it means NaN values will always bubble to the end of the sorted slice. If your domain requires NaN to be treated as zero or filtered out, you must normalize the values before comparison.

Pointer comparison follows value semantics, not address semantics. If you pass two pointers to cmp.Compare, the function compares the memory addresses, not the underlying values. This is intentional. Pointer equality in Go means identity, not structural equality. If you need to compare the values behind pointers, dereference them first or compare the dereferenced types directly. The compiler will catch mismatched pointer types with cannot use *int as int value in argument.

The package also exports Less, Greater, Equal, Max, and Min. These functions wrap cmp.Compare and return booleans or the selected value. They are useful when you need a simple conditional check without caring about the full ordering signal. cmp.Max(a, b) returns the larger value. cmp.Min(a, b) returns the smaller value. They panic if the inputs are uncomparable, just like cmp.Compare.

Go conventions favor explicit naming and clear boundaries. Public functions start with a capital letter. Private helpers start lowercase. The cmp package follows this rule strictly. You will see cmp.Compare exported, but internal helpers like cmp.compareInt kept private. Trust the exported surface. Do not reach for internal reflection tricks to compare unexported fields.

Floating point NaN sorts last. Pointers compare addresses. Normalize before you sort.

When to reach for cmp

The standard library provides multiple ways to order values. Choosing the right tool depends on your data shape and performance requirements.

Use cmp.Compare when you need a generic comparator that works with slices.SortFunc or slices.BinarySearchFunc. Use cmp.Less or cmp.Greater when you only need a boolean check inside an if statement. Use cmp.Max or cmp.Min when you need the extreme value from a pair without writing a conditional. Use manual <, >, and == operators when you are comparing simple primitives in a tight loop and want to avoid function call overhead. Use a custom comparator function when you need multi-field sorting, case-insensitive string comparison, or domain-specific ordering rules.

The cmp package is a standardization layer, not a performance optimization. It removes boilerplate and aligns your code with the standard library's expectations. Keep your comparators small. Pass them directly to slices functions. Let the compiler enforce type safety.

Goroutines are cheap. Comparators should be pure.

Where to go next