AlgoMaster Logo

constraints Package

Low Priority25 min readUpdated June 6, 2026

The golang.org/x/exp/constraints package ships a small set of named type constraints (Ordered, Integer, Float, Signed, Unsigned, Complex) that almost every generic Go function ends up needing. Instead of hand-rolling a type Number interface { ~int | ~int8 | ... } in every file, you import the package and use what's already there. This chapter covers what each constraint actually contains, how to compose them, why the package still lives under x/exp, and how Go 1.21's stdlib cmp.Ordered together with the slices and maps packages changes the picture.

Why a Standard Set of Constraints

When you write a generic function that does math or comparison, you keep needing the same handful of constraints over and over. Sort a slice? You need "anything that supports <." Sum a slice? You need "any numeric type." Bucket histogram counts? You need "any integer type."

Without a shared library, every file ends up with its own copy of the same type set:

These definitions show up in repo after repo, often with slight inconsistencies. One file forgets uintptr. Another writes int without the ~ and breaks for named types like type Cents int. A third spells the constraint Numeric instead of Number, so two halves of the codebase can't share a generic function.

The constraints package solves this by being the canonical definition. When you import it, every other Go developer who reads your code immediately knows what constraints.Ordered means, what types it admits, and how it relates to the other constraints in the package.

Read the diagram as containment. Ordered is the widest of the comparison constraints: it admits every type that supports the <, <=, >, >= operators. That means all integers, all floats, and string. Integer is narrower; it admits only integer types, split further into Signed and Unsigned. Complex sits off to the side because complex numbers aren't ordered (Go doesn't define < on them), so they can't appear under Ordered.

Cents, Quantity, Price, and every other named numeric type you'll define in an e-commerce program satisfies the relevant constraint automatically, thanks to the ~ tilde approximation that we'll look at next.

Installing and Importing

The package lives outside the standard library, in the golang.org/x/exp experimental repository. You bring it in like any other module dependency:

This adds the requirement to your go.mod file. After that, import it like any package:

A minimal go.mod after running the go get looks roughly like this:

The exact version string is a pseudo-version because the x/exp repo doesn't tag releases. Your version will differ; let go get and go mod tidy manage it.

Here's the smallest useful program that uses the package. It computes the minimum of three prices typed as Cents (a named integer type):

Cents is a named type whose underlying type is int64. It satisfies constraints.Ordered because the constraint uses the ~ tilde, which we'll dissect in the next section.

constraints.Ordered

Ordered is the most-used constraint. It captures every Go type that supports the four ordering operators. The package's source definition is equivalent to this:

Two details matter. First, every builtin ordered type is listed. Second, every entry uses ~ (the tilde), which says "any type whose underlying type is this one." That second detail is what makes Cents, Quantity, and your other named types satisfy Ordered without any extra work.

Compare these two attempts:

constraints.Ordered is the tilde version. That's why a function constrained by it accepts e-commerce types like Cents, Quantity, and Sku without you ever asking.

Here's a tiny in-place sort across Cents, Quantity, and string order names. One generic function handles all three:

sortAsc doesn't care what T is, only that values of T can be compared with <. constraints.Ordered enforces exactly that property at compile time. If you tried to call sortAsc with []bool, the compiler would refuse, because bool doesn't satisfy Ordered.

A common use is a generic max for any ordered type. Here's one that finds the most expensive product in a cart:

T is unconstrained (any) because the function doesn't compare T values directly. It compares the keys returned by key(item), which are constrained to Ordered. This pattern shows up constantly: sort, max, min, group by, and so on all want an Ordered key.

constraints.Integer, Signed, Unsigned

Integer admits every integer type, signed or unsigned. Signed admits just the signed ones. Unsigned admits just the unsigned ones. The package definitions look roughly like this:

Integer is literally a union of Signed and Unsigned via the | operator at the type set level. That's a feature of constraint interfaces: you can embed other constraint interfaces, and the type set becomes the union.

Which one should you pick?

ConstraintUse when
IntegerThe function works for any integer type. Counts, indexes, IDs, histogram bins.
SignedYou need negative values. Differences, deltas, signed offsets.
UnsignedYou're working with sizes, bit masks, or values that can't be negative by definition.

Histogram bucketing is a clean fit for Integer. Quantities, rating counts, and similar e-commerce values are non-negative, but the function itself doesn't need to forbid signed types, so Integer is the right scope:

If you had used constraints.Ordered instead, the function body would fail to compile, because / and % aren't defined on string or float. Integer is the right level of strictness here: any arithmetic that requires integer division or remainder.

Signed shows up when negative values are part of the design. A priceDelta that compares last week's price against this week's must produce negatives:

If T were constrained to Unsigned and previous[i] > current[i], the subtraction would wrap around and produce a giant positive number rather than a negative. The constraint here is documentation: this function expects a signed integer because it returns possibly negative results.

Unsigned is rarer in application code, more common in low-level work. A common example is computing a checksum or a bitmap mask. For e-commerce code, the typical use is when a value's domain genuinely cannot be negative, such as a Stock count modeled as uint32:

The choice between Integer and Unsigned here is mild. Integer would also compile. Unsigned documents the invariant that the values being summed cannot be negative. In production, Integer is the safer default unless you have a specific reason to forbid signed types.

constraints.Float and Complex

Float admits the two builtin floating-point types and their named variants. Complex admits the two builtin complex types. The definitions are short:

Numerical code is the natural home for these. Averaging prices, computing weighted scores, or smoothing a rating series all want Float:

T(len(values)) is the standard pattern: you convert the integer count into your generic float type. Go's type rules require you to do this explicitly; the compiler won't mix T and int for division.

A weighted score function is a slight extension. Here's one that computes a weighted average review score for a product, where each review has a numeric score and a numeric weight (e.g., a verified-buyer review counts more):

Complex is rarely needed in application code. It exists for completeness because complex numbers are built into Go. Signal processing, FFT-style code, and some graphics work use it. A demonstration is the most this lesson needs:

constraints.Complex is listed here for completeness. Most business applications don't use complex numbers.

Composing Constraints

You can build new constraints by embedding others. The semantics here are precise and worth pinning down.

When a constraint interface embeds two type sets, the resulting type set is the intersection of those sets, not the union. That's how Go's interface satisfaction works: a type must satisfy every embedded interface to satisfy the outer one.

Look at this attempt to write "any integer or float":

The set of types that are both integers and floats is empty. No type satisfies both. A function constrained by IntOrFloat cannot be instantiated with any type, and any attempt produces a compile error.

To get the union (any integer or float), you have to use the | operator inside one interface, not separate embedded lines:

constraints.Integer | constraints.Float is one type set: take the type set of Integer, take the type set of Float, take their union. Now int, int32, float64, and named types like Cents and Rating all satisfy Number.

Here's a full program that defines Number and uses it to sum any numeric slice:

The Number constraint is reusable across the codebase. Plenty of projects define exactly this constraint, often alongside a Real (numeric without complex) or Numeric (numeric including complex) alias.

A subtle case: what if you want "ordered values that are also numeric"? That's where the intersection semantics of multi-line embedding are actually useful:

In this specific case, the intersection equals the union side because everything in Integer | Float is already in Ordered. So OrderedNumber is equivalent to Integer | Float, just spelled differently. Most code prefers the simpler Number form.

The diagram contrasts the two operations. On the right, Number = Integer | Float is the union, which is what you almost always want. On the far right, embedding both as separate lines gives the intersection, which is empty and therefore useless as a constraint.

Go 1.21: cmp.Ordered in the Standard Library

Go 1.21 promoted the most useful piece of constraints into the standard library as cmp.Ordered. Its definition is the same as constraints.Ordered:

The cmp package also ships cmp.Compare[T Ordered](x, y T) int and cmp.Less[T Ordered](x, y T) bool, the building blocks the rest of the stdlib needs for sorting and ordering.

Here's the same minimum example as before, but using cmp.Ordered from the stdlib instead of the x/exp version:

Same behavior, no external dependency. If your project is on Go 1.21 or newer and you only need Ordered, prefer cmp.Ordered.

What about the other constraints? At the time of writing, the stdlib only promoted Ordered (plus a few helpers in cmp). Integer, Float, Signed, Unsigned, and Complex are still only in golang.org/x/exp/constraints. If you need any of those, you still import the x/exp package.

A reasonable rule of thumb:

NeedUse
Just Ordered and you're on Go 1.21+cmp.Ordered (stdlib)
Anything else (Integer, Float, Signed, Unsigned, Complex)constraints.X (x/exp)
Go 1.18-1.20constraints.X for everything

A small program that uses both packages side by side to make the boundary explicit:

clampPrice uses cmp.Ordered because ordering is all it needs. totalUnits uses constraints.Integer because it needs the operator behavior of integers (in this case, just +=, but more generally to forbid floats).

Pair with slices and maps Packages (Go 1.21+)

The slices and maps packages were promoted into the standard library in Go 1.21. Almost every function in them is generic, and most of the ordering-aware ones use cmp.Ordered. That means you rarely have to write minOf, maxOf, sortAsc, or contains by hand anymore. The stdlib has them.

A short selection of what slices ships:

FunctionWhat it doesConstraint
slices.SortSorts in place by <cmp.Ordered
slices.SortFuncSorts using a custom comparatorany element
slices.Min, slices.MaxReturns smallest/largestcmp.Ordered
slices.ContainsReports whether a value is presentcomparable
slices.IndexReturns the index of a value, or -1comparable
slices.ReverseReverses in placeany

The following program uses several of them on a slice of Product values:

Two interesting bits here. First, slices.SortFunc doesn't need Ordered, because the comparator does the comparing. That's why we can sort Product (which isn't itself Ordered) with it. Inside the comparator, we use cmp.Compare on the Cents field, which is Ordered.

Second, slices.Min and slices.Max work directly on []Cents because Cents satisfies cmp.Ordered. You don't write minOf yourself.

A useful pattern with maps. The maps.Keys function (Go 1.23+ in stdlib, earlier in x/exp) returns an iterator over a map's keys. Here's a quick example that builds a price lookup and then iterates the keys deterministically by sorting them first:

maps.Keys produces an iterator (Go 1.23 iterator type), and slices.Sorted consumes it and returns a sorted slice. The two packages dovetail through generic constraints: slices.Sorted is constrained by cmp.Ordered, and the keys are string, which satisfies it.

When NOT to Use the constraints Package

Reaching for constraints.Ordered or constraints.Integer is usually right, but a custom type set is sometimes clearer or smaller.

Use a custom constraint when:

  • You need a strictly narrower set. If your function should only work with int32 and int64 (say, because the algorithm relies on 32-bit-or-wider arithmetic), write interface { ~int32 | ~int64 }. constraints.Integer is too permissive.
  • You need method requirements, not just type sets. Constraints can require methods too. A function that converts any value to a string for logging should constrain by interface{ String() string }, not by constraints.Ordered. The constraints package is for value types and operators only.
  • You're writing a tutorial or a library API. A custom constraint named Number or Score reads better in documentation than an imported constraints.X. The constraint name communicates intent.
  • You want zero external dependencies. golang.org/x/exp/constraints is a single small package, but if your project is otherwise stdlib-only and on Go 1.21+, defining your own Number interface (or using cmp.Ordered) avoids pulling in x/exp.

A concrete example. Suppose you're writing a checksum function that XOR-folds 32-bit or 64-bit integers only, because narrower types might cause aliasing issues you want to avoid. constraints.Integer would accept int8 and uint16, which you don't want:

WideInt is custom because the standard Integer is wider than needed. The constraint name documents the rule, the type set enforces it, and the function refuses narrow types at compile time.

Another situation: a generic logger that needs String(). Here, constraints.Ordered would be meaningless. What you want is a method constraint:

The constraint here is a method set, not a type set. constraints.Ordered couldn't express this. Writing a custom interface is the only option, and it's the natural one.

The summary rule: use constraints (or cmp.Ordered) when the function's correctness depends on operators that work the same way across many builtin types. Use a custom constraint when you need a narrower type set, method requirements, or a name that communicates intent.

Quiz

constraints Package Quiz

10 quizzes