Go 1.27 Generic Methods: Type Parameters and Interface Limits
Use Go 1.27 generic methods, distinguish receiver and method type parameters, and design around the interface-method restriction.
Go 1.27 lets a concrete method declare type parameters after its name. A method can now introduce a result type that is not fixed by its receiver, so operations such as List[int].Map can return List[string] without becoming package-level functions. An interface method still cannot declare type parameters, and instantiating a generic method does not make its receiver implement an ordinary interface method with the resulting signature.
This syntax requires the Go 1.27 language version. Use a Go 1.27 or newer toolchain and set the module’s go directive to at least 1.27; the go directive controls the language version enforced for a module as well as its minimum toolchain requirement. Go 1.18 introduced generic types and functions, but method-specific type parameters arrived in Go 1.27.
A method can now introduce its own type
Here is a complete Go 1.27 program. E belongs to the receiver type. R belongs to each call of Map.
package main
import (
"fmt"
"strconv"
)
type List[E any] []E
func (list List[E]) Map[R any](f func(E) R) List[R] {
result := make(List[R], len(list))
for i, value := range list {
result[i] = f(value)
}
return result
}
func main() {
numbers := List[int]{10, 20, 30}
labels := numbers.Map(strconv.Itoa)
fmt.Println(labels)
decorate := numbers.Map[string]
fmt.Println(decorate(func(n int) string {
return fmt.Sprintf("n=%d", n)
}))
}
It prints:
[10 20 30]
[n=10 n=20 n=30]
The grammar change is small. The Go specification now permits a type parameter list after a concrete method name:
func (receiver ReceiverType) Method[MethodType any](argument MethodType) {}
The receiver does not have to be generic. A non-generic type can also declare Method[T any]. What matters is that the receiver base type is a defined, non-interface type declared in the same package as the method, as with other method declarations.
Receiver parameters and method parameters have different lifetimes
The brackets in List[E] and Map[R any] do different jobs:
type List[E any] []E
func (list List[E]) Map[R any](f func(E) R) List[R]
Instantiating List[int] fixes E as int for the value and all its methods. Calling Map chooses R for that call. The same List[int] value can therefore produce List[string], List[bool], or another result type without changing the receiver’s instantiation.
In the receiver declaration, List[E] is not choosing an already declared type named E. It declares a receiver type parameter corresponding to the type parameter on List. Its constraint is inherited from the type definition. The method’s R any is a separate declaration with its own constraint.
Before Go 1.27, a method could use only the receiver’s type parameters. A transformation whose result introduced another type needed a package-level generic function such as MapList[E, R](List[E], func(E) R) List[R]. That function remains a sound compatibility choice when a package must support Go 1.26 or earlier.
Calls can infer method type arguments
In numbers.Map(strconv.Itoa), the compiler infers R as string from the function argument. The instantiation and inference rules apply to generic methods as well as generic functions. Type arguments may be explicit, partial, or omitted when the call or another permitted context supplies enough type information.
Inference cannot invent information that is absent. If a method type parameter appears only in its result, a direct call such as Factory{}.Zero() gives the compiler nothing from which to infer that type. Supply it explicitly, or give the uninstantiated method a target function type:
type Factory struct{}
func (Factory) Zero[T any]() T {
var zero T
return zero
}
var value = Factory{}.Zero[string]()
var zeroString func() string = Factory{}.Zero
The second declaration also shows that generic methods work on non-generic receiver types. When an API makes callers write type arguments repeatedly, look at the signature before blaming inference. Moving the type parameter into an ordinary argument often gives the compiler the missing relationship and makes the call clearer.
Constraints still govern the body. A method may use only operations guaranteed by its method-specific constraint, just as a generic function may. Instantiation fails when an explicit or inferred type argument does not satisfy that constraint.
Instantiate method values and method expressions
A generic method must become non-generic before code can call it or store it as a function value. The first program does that explicitly here:
decorate := numbers.Map[string]
decorate binds numbers as the receiver and has type func(func(int) string) List[string]. A method expression leaves the receiver as the first ordinary parameter:
mapIntsToStrings := List[int].Map[string]
labels := mapIntsToStrings(numbers, strconv.Itoa)
The resulting function has type func(List[int], func(int) string) List[string]. The official Go 1.27 generic-methods article shows the same explicit instantiation rule for method values and expressions. A bare assignment such as f := numbers.Map fails when no context determines the method type arguments.
A generic method does not satisfy an interface method
This compile-time assertion fails even though List[int].Map[string] has the same parameter and result types as the method in StringMapper:
type StringMapper interface {
Map(func(int) string) List[string]
}
var _ StringMapper = List[int]{} // does not compile
List[int] declares the generic method Map, not a collection of separately declared methods such as Map[string]. Interface implementation is a property of the type’s method set; it is not recalculated for one selected instantiation. The Go 1.27 release notes state this boundary directly: generic methods cannot implement interface methods.
Nor can the interface copy the method’s type parameter list:
type Mapper interface {
Map[R any](func(string) R) []R // invalid in Go 1.27
}
Go 1.27 permits type parameters on concrete methods only. The official generic-methods design explanation describes the implementation problem: a separately compiled caller could request an instantiation that the package declaring the concrete type could not know to generate. Go does not solve that problem with runtime code generation or a boxed, indirect calling convention.
A generic interface is a different tool
An interface type may have type parameters, but each method in an instantiated interface has an ordinary, fixed signature:
type Decoder[T any] interface {
Decode([]byte) (T, error)
}
type User struct {
Name string
}
type UserDecoder struct{}
func (UserDecoder) Decode(data []byte) (User, error) {
return User{Name: string(data)}, nil
}
var _ Decoder[User] = UserDecoder{}
Decoder[User] requires exactly Decode([]byte) (User, error). UserDecoder provides that non-generic method, so it implements the instantiated interface. This pattern lets generic functions constrain related types, but it does not let callers choose fresh type arguments on an interface method.
Do not replace a useful ordinary interface merely to use the new syntax. If callers need dynamic dispatch through an interface, keep the method signature non-generic and put any variability on the interface type, on an enclosing generic function, or in ordinary arguments.
Choose methods for concrete fluent APIs
Generic methods fit concrete APIs where an operation belongs naturally beside a type and each call may introduce a new type. Mapping, decoding, querying, and builder-style chains are plausible uses. The standard library demonstrates the organizational benefit in Go 1.27 by adding a generic N method to *rand.Rand in math/rand/v2, alongside the existing package-level generic rand.N, as recorded in the Go 1.27 release notes.
Keep a package-level generic function when supporting older Go versions, when the operation treats several inputs symmetrically, or when interface-based substitution matters. A method name also occupies that name on its receiver type, so adding one is an API choice rather than free namespacing.
The practical rule is narrow. Use Method[T] when callers operate on a concrete receiver and the method needs a type that the receiver does not already provide. Use an ordinary method for interface contracts. Use a parameterized interface when the whole contract should be instantiated to fixed method signatures.