skip to content

Parametric Methods

Go 1.27 lets a method declare type parameters of its own, with two catches: an interface cannot declare one, and such a method never satisfies an interface method. Older material calls it impossible.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

3

In Go, what does it mean for a method to declare its own type parameter?

level: juniorimportance: should knowfreq 22%

answer

  1. where do the square brackets sit
  2. receiver brackets versus name brackets
  3. who picks the type: the value or the call
  4. fixed at instantiation, or fresh per call
  5. method type parameter lists arrived in Go 1.27

basics

~20 s

Since Go 1.27 a method may list a type parameter after its own name, and each call site picks a type for it. That is different from a method on a generic type, which declares nothing new and simply reuses the parameter its receiver type was instantiated with.

solid answer

~40 s

A parametric method writes its type parameter list after the method name: `func (a Adapter) Decode[F any](frame []byte) (F, error)`. Here `F` belongs to the method, so one `Adapter` value can be used with many different `F` types, chosen per call. That is new in Go 1.27; before it, a method declaration could not carry type parameters at all and you had to write a package-level generic function taking the receiver as an argument. The older, still-common shape is a method on a *generic type*: `func (f *Framer[T]) Add(v T)`. There `T` is not declared by the method — it comes from the receiver type `Framer[T]`, is fixed once the type is instantiated as `Framer[Frame]`, and is shared by every method on that value.

code

go · 13 lines
go
type Framer[T any] struct{ frames []T }

// Add reuses T from the receiver type: legal since Go 1.18.
func (f *Framer[T]) Add(v T) { f.frames = append(f.frames, v) }

type Adapter struct{ topic string }

// Decode declares its own F: a method type parameter, legal since Go 1.27.
func (a Adapter) Decode[F any](frame []byte) (F, error) {
	var out F
	err := json.Unmarshal(frame, &out)
	return out, err
}

go deeper

for a junior

Be ready to point at a declaration and say where the type parameter is declared: in the receiver brackets, so it belongs to the type, or after the method name, so it belongs to the method. Know that the second form needs Go 1.27.

for a middle

Explain the lifetime difference: a receiver's type argument is fixed when the value is created, while a method's is chosen at every call site, usually by inference from the arguments or written explicitly. Also know the pre-1.27 workaround of a package-level generic function.

for a senior

Show judgment about where the parameter belongs. Putting it on the method buys per-call flexibility and costs you the ability to satisfy any interface with that method, which is a change your package's consumers feel.

for a principal

Frame it as a commitment point: a parameter on the type makes callers choose once at construction, a parameter on the method makes them choose everywhere. The second is harder to migrate away from because every call site carries the decision.

## Two different things that both look "generic" Go has had type parameters since 1.18, but until Go 1.27 they could appear in only two places: on a function declaration and on a type declaration. A method declaration could *use* type parameters, but only ones it inherited from its receiver type. Go 1.27 added a third place: a method may now declare a type parameter list of its own. ### A method on a generic type ```go type Framer[T any] struct{ frames []T } func (f *Framer[T]) Add(v T) { f.frames = append(f.frames, v) } ``` The `[T]` in the receiver `(f *Framer[T])` is not a new declaration in the sense of introducing freedom — it binds a name to the type argument the receiver was created with. If you build a `Framer[Frame]`, then for that value `T` *is* `Frame`, permanently, in every method. `Add` cannot be called with anything else, and two calls on the same value always use the same `T`. This shape has been legal since generics landed in 1.18. ### A method with its own type parameter ```go type Adapter struct{ topic string } func (a Adapter) Decode[F any](frame []byte) (F, error) { var out F err := json.Unmarshal(frame, &out) return out, err } ``` Here `F` is declared by `Decode` itself, between the method name and the parameter list. The receiver type `Adapter` is an ordinary, non-generic struct. Every call chooses its own `F`: one call site can decode into a `Heartbeat`, the next into a `Payload`, using the *same* `Adapter` value. That is what "parametric method" means, and in Go it is a Go 1.27 feature. ### What you wrote before 1.27 Before Go 1.27 the compiler rejected any method declaration that listed type parameters. The standard workaround was to hoist the operation out of the method set into a package-level generic function whose first argument is the value the method would have had as its receiver: ```go func DecodeFrame[F any](a Adapter, frame []byte) (F, error) ``` This is why so much idiomatic Go code exposes package-level functions where another language would expose methods — the standard library's slice and map helpers are functions, not methods, for exactly this reason. That workaround has not gone away and is still the right answer whenever the operation must remain reachable through an interface (see below). ### The restriction that comes with the feature Generic methods are deliberately limited in Go 1.27: - An **interface** may not declare a method with its own type parameters, in any Go version. Since a constraint is just an interface, a constraint cannot require one either. - A **generic method cannot implement an interface method**. A type that has one still satisfies interfaces through its *other* methods, but the parametric method itself never counts toward satisfaction. The reason is dispatch. An interface value carries a table with exactly one function pointer per interface method, filled in when the concrete type is assigned. A method whose type parameter is chosen at the call site has an open-ended family of instantiations, so there is no single entry to put in that table. ### How to tell them apart when reading code Look at where the brackets are: - Brackets in the **receiver** (`func (f *Framer[T]) Add(v T)`): the parameter belongs to the type. Fixed for the life of the value. - Brackets after the **method name** (`func (a Adapter) Decode[F any](...)`): the parameter belongs to the method. Chosen per call. Go 1.27 or later. - Brackets after a **function name** with no receiver (`func DecodeFrame[F any](a Adapter, ...)`): an ordinary generic function. ### Why it matters for API shape Moving a type parameter from the type onto the method changes who chooses it. On the type, the caller commits once, at construction, and everything downstream is fixed. On the method, the caller commits at each call and can use one value many ways. That flexibility is real, but it costs you interface satisfaction for that method — so if consumers of your package depend on an interface, the operation belongs in a package-level function or in a method with a concrete signature.

  • If a struct is not generic at all, can one of its methods still be generic?
    Yes, from Go 1.27 on. The receiver type can be a plain non-generic struct while the method declares its own type parameter after the method name. The parameter is scoped to that one method; other methods on the same type know nothing about it, and the value carries no type argument.
  • Can a method on a generic type also declare a type parameter of its own?
    The receiver's parameters and a method's own parameters are separate lists, and the method body sees both. Keep this rare: two levels of type parameter on one call are hard to read, and the method still cannot implement any interface method. If you find yourself needing it, a package-level generic function taking the receiver as an argument is usually the clearer API.
  • Why do the slices and maps standard packages expose functions rather than methods?
    They predate Go 1.27, when a method could not declare type parameters. An operation like transforming a slice of one element type into a slice of another needs a second type parameter that the receiver type does not supply, so it had to be a package-level function. That shape is now conventional and did not change when generic methods arrived.

A method on a generic type is a machine built for one part size at the factory. A parametric method is one machine with a changeable die you fit at each job.

saying these in an interview costs you the question

  • Says a method on Framer[T] declares T itself
  • Thinks Go 1.18 already allowed type parameters on methods
  • Puts the method's brackets before the method name
  • Claims the receiver value stores the method's type argument
  • Assumes a generic method can appear in an interface
open as a page

Why can a Go interface never declare a method that has its own type parameter?

level: middleimportance: should knowfreq 30%

basics

~20 s

An interface value dispatches through a table holding one function pointer per method, filled in when a concrete type is assigned. A method whose type argument is picked at the call site has an unbounded family of instantiations, so there is no single entry to store. Interfaces therefore forbid it.

open as a page

Your Adapter.Decode method gained its own type parameter and Adapter no longer satisfies your exported Decoder interface. How do you reshape the API?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Move the type parameter off the method. Put it on a package-level generic function that takes the adapter as an argument, or on the interface type itself, and leave the method with a concrete signature so it keeps satisfying the interface. A parametric method can never implement an interface method.

open as a page