What Is the any Constraint in Go Generics

The `any` constraint is a type alias for `interface{}` that allows Go generics to accept values of any type without restrictions.

The catch-all constraint

You are writing a utility function to wrap values in a pointer. You want it to work for int, string, and your custom User struct. In pre-generics Go, you would write func Pointer(v interface{}) interface{}. That compiles, but the return type is interface{}, which throws away the concrete type information. The caller gets a generic blob and must use type assertions to recover the original type. Generics solve this. You write func Pointer[T any](v T) *T. The any constraint tells the compiler to accept any type for T while preserving that type in the return value. The caller gets *User when they pass a User, and *int when they pass an int.

What any actually is

any is not a new type. It is a type alias defined in the builtin package. The definition is type any = interface{}. It is exactly the empty interface. The empty interface has no methods, so every type in Go satisfies it. any exists purely for readability. Writing func F[T interface{}] looks like you are dealing with dynamic typing or type erasure. Writing func F[T any] signals that T is a generic placeholder. The compiler treats them identically.

The community prefers any in generic constraints because it reduces visual noise. interface{} is four characters and carries the mental weight of type assertions and reflection. any is three characters and reads like a variable name. It makes generic signatures scan faster.

any is a readability alias. It changes how you read the code, not how the compiler generates it.

Minimal example

Here is a generic function that prints a value using any.

// PrintAny accepts any type and prints it.
func PrintAny[T any](v T) {
	// T is unconstrained, so the compiler allows any type argument.
	// fmt.Println accepts interface{}, so it works for all T.
	fmt.Println(v)
}

When you call PrintAny(42), the compiler sees that T is instantiated with int. It generates a version of the function where T is replaced by int. When you call PrintAny("hello"), it generates a version where T is string. The any constraint imposes no restrictions on T. The function body must only use operations that are valid for every type, or the compiler rejects the code.

Reification and performance

Go generics are reified. The type parameter is known at compile time. This matters for performance. When you pass an int to a non-generic function like func F(v interface{}), the compiler boxes the value. It allocates an interface value on the heap or stack that holds a pointer to the type descriptor and a pointer to the data. This boxing can trigger allocations and hurt performance in tight loops.

When you pass an int to func F[T any](v T), the compiler generates func F_int(v int). There is no boxing. The value is passed directly. The type is preserved. Escape analysis can often keep the value on the stack. any gives you the flexibility of interface{} with the performance of concrete types.

Generics reify types. any keeps the type known at compile time.

When any is too loose

any accepts everything, but that means you cannot assume anything about the type. You cannot perform arithmetic. You cannot compare values with == if the type might be a slice, map, or function, because those types are not comparable. The compiler enforces these rules at the point of definition, not at the point of call.

Here is a function that tries to add one to a value.

// Increment tries to add one to a value.
func Increment[T any](v T) T {
	// This fails to compile.
	// The compiler rejects this with: invalid operation: v + 1 (operator + not defined on T)
	return v + 1
}

The compiler rejects this immediately. T could be string, and you cannot add one to a string. The constraint any does not guarantee that + exists. You need a tighter constraint for arithmetic.

Here is a function that tries to check equality.

// Contains checks if a slice includes a target.
func Contains[T any](slice []T, target T) bool {
	for _, item := range slice {
		// This fails to compile.
		// The compiler rejects this with: invalid operation: item == target (operator == not allowed on T)
		if item == target {
			return true
		}
	}
	return false
}

This also fails. T could be []int, and slices do not support ==. The compiler requires the comparable constraint for equality checks. any is the floor of constraints. It promises nothing except that the type exists.

Constraints are promises. any promises nothing except existence.

Realistic example: Generic pointer helper

A common use of any is a helper that returns a pointer to a value. This is useful when you have a struct field that requires a pointer, but you only have a value.

// Pointer returns a pointer to the value.
func Pointer[T any](v T) *T {
	// Return the address of v.
	// Taking an address is valid for all types.
	// The return type *T preserves the concrete type for the caller.
	return &v
}

This function works for int, string, struct, and custom types. The caller gets the correct pointer type without casting.

// Usage example.
func main() {
	// p has type *int.
	p := Pointer(42)
	fmt.Println(*p)

	// s has type *string.
	s := Pointer("hello")
	fmt.Println(*s)
}

The any constraint is perfect here. The function does not care about the type. It only needs to take an address and return it. The type information flows through the generic parameter.

Pitfalls and errors

Using any can lead to subtle bugs if you forget that the type is unknown. You might write code that compiles but panics at runtime if you misuse type assertions inside the function. Generics are meant to avoid type assertions. If you find yourself type-switching inside a generic function, you probably need a tighter constraint or a different design.

Another pitfall is using any when you actually need comparable. Developers often write func F[T any] and then try to use T as a map key. The compiler rejects this with invalid operation: map[T] (T is not comparable). You must use func F[T comparable] for map keys.

The compiler also rejects methods on any that do not exist on all types. You cannot call .Len() on T if T is any. You get T has no field or method Len. You need an interface constraint like interface{ Len() int } for that.

Do not fight the type system. Wrap the value or change the design.

Convention asides

The any alias is part of the builtin package, so you do not need to import anything to use it. It is available everywhere.

When writing generic code, the community prefers any over interface{} in constraints. If you see func F[T interface{}] in a codebase, it is likely older code or written by someone who missed the alias. The style guide encourages any for readability.

Receiver naming conventions apply to generic types too. If you define a method on a generic type, use a short receiver name. (s *Slice[T]) Sort() is standard. Do not use (self *Slice[T]). The receiver name should be one or two letters, usually matching the type name.

gofmt handles the spacing around generic parameters. It ensures func F[T any] has consistent spacing. Trust gofmt. Argue logic, not formatting.

Decision matrix

Use any when you need a generic parameter that accepts every type and you only perform operations valid on all types, like passing to fmt or taking an address.

Use comparable when you need to compare values with == or use the type as a map key.

Use a method-based interface constraint when you need to call specific methods on the type.

Use interface{} in non-generic code when you need a catch-all parameter in a function signature that predates generics or cannot use them.

any is the floor, not the ceiling. Pick the constraint that matches your needs.

Where to go next