Generic methods in Go 1.27, and the one thing they still can’t do

By

Go 1.27 shipped on 19 August 2026 with the language change I’ve wanted most since generics arrived in Go 1.18: a method can now declare its own type parameters. Until now a method could use its receiver’s type parameters but couldn’t add any of its own, so anything that needed a new type ended up as a top-level function sitting next to the type it really belonged to.

This post covers what changes in everyday code, the rule that generic methods can’t satisfy interfaces, and why the Go team drew that line where it did.

The shape we’ve been writing since 1.18

Take a paginated API result. The page type is generic over its items, and sooner or later you need to turn a page of database rows into a page of response objects.

type Page[T any] struct {
	Items      []T
	NextCursor string
}

// before Go 1.27: a method can't introduce R, so this has to be a function
func MapPage[T, R any](p Page[T], f func(T) R) Page[R] {
	out := Page[R]{Items: make([]R, 0, len(p.Items)), NextCursor: p.NextCursor}
	for _, item := range p.Items {
		out.Items = append(out.Items, f(item))
	}
	return out
}

It works, but two steps in a row read inside-out: MapPage(MapPage(p, toDTO), redact). The step that runs last is the first thing you read.

What changes in 1.27

The same function becomes a method. The receiver supplies T, and the method declares R right after its name:

func (p Page[T]) Map[R any](f func(T) R) Page[R] {
	out := Page[R]{Items: make([]R, 0, len(p.Items)), NextCursor: p.NextCursor}
	for _, item := range p.Items {
		out.Items = append(out.Items, f(item))
	}
	return out
}

resp := rows.Map(toDTO).Map(redact)

Type arguments are inferred from the call’s arguments, just as they are for generic functions, so .Map(toDTO) needs no brackets. The release notes put the whole change in one sentence: “a method declaration may declare its own type parameters.”

Hand-drawn comparison of the same two-step transformation. Top, before Go 1.27: nested function calls, MapPage(MapPage(p, toDTO), redact), drawn as a box inside a box, so the step that runs first sits in the middle and you read it inside-out. Bottom, with Go 1.27 generic methods: a left-to-right pipeline, p of type Page[User], then .Map(toDTO) gives Page[UserDTO], then .Map(redact) gives a redacted Page[UserDTO].
Nested generic functions read inside-out. Generic methods read in the order they run.

Chaining is the obvious win, but the case I expect to use more is a non-generic type with one generic method. An HTTP client that decodes JSON into whatever type you ask for has always had to be a function that takes the client as an argument. Now it can be a method:

type Client struct {
	base string
	http *http.Client
}

func (c *Client) GetJSON[T any](ctx context.Context, path string) (T, error) {
	var out T
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.base+path, nil)
	if err != nil {
		return out, err
	}
	resp, err := c.http.Do(req)
	if err != nil {
		return out, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return out, fmt.Errorf("GET %s: %s", path, resp.Status)
	}
	err = json.NewDecoder(resp.Body).Decode(&out)
	return out, err
}

user, err := client.GetJSON[User](ctx, "/users/42")

Here T appears only in the result, so there’s nothing to infer it from and you write it explicitly. The standard library uses the feature too: math/rand/v2 gains a generic (*Rand).N method.

One rule from the Go blog post on generic methods is worth knowing early: a generic method has to be instantiated, explicitly or by inference, before it’s called or turned into a function value. A method expression names both sets of type arguments, so Page[User].Map[UserDTO] is an ordinary function that takes the page as its first argument.

The one thing they can’t do: satisfy an interface

This is the rule that will catch people. From the release notes: “methods of interfaces may not declare type parameters nor can interface methods be implemented by generic methods.”

The first half means you can’t write Map[R any](...) inside an interface. The second half is subtler. Suppose an interface asks for a method whose signature exactly matches one instantiation of a generic method:

type Labeler interface {
	Label(int) string
}

type Formatter struct{}

func (Formatter) Label[T any](v T) string { return fmt.Sprint(v) }

var _ Labeler = Formatter{} // does not compile: a generic method never satisfies an interface

Formatter{}.Label[int] has exactly the signature Labeler asks for, and it still doesn’t count. The blog post explains why: “Interface implementation is a property of a (possibly instantiated) type, not of any particular method.” Formatter declares the generic Label, not Label[int].

Why the line is there

It comes down to how Go compiles. A call through an interface goes through a method table: one slot per method, each pointing at code that already exists for the concrete type. A direct call such as page.Map[string](f) is easy, because the type argument is right there at the call site and the compiler generates exactly that instantiation.

A generic method called through an interface would need code for every type argument anyone might ever pass, including types from packages that haven’t been written yet. The blog post calls that “impractical with Go’s approach to instantiation.” The alternative, boxing the arguments so that every instantiation shares one implementation, would make even direct calls pay for an indirect call. So Go 1.27 ships the useful half: type parameters on methods of concrete types, and none on interface methods.

Hand-drawn diagram of why a generic method cannot satisfy an interface. Left, a direct call: the compiler sees page.Map[string](f), knows the type argument at the call site, and generates exactly one instantiation, Map[string]. Right, a call through an interface value: the interface method table for Page has one slot per method, pointing at compiled code. A generic Map would need a slot for every possible type argument, Map[int], Map[string], Map[User] and types from packages that do not exist yet, so the table cannot be built ahead of time.
A direct call knows its type argument. An interface method table would need a slot for every type argument there could ever be.

How I’ll use them

  • Typed helpers on clients and stores. client.GetJSON[T], or a repository method that scans rows into a type the caller picks, now live on the type instead of next to it.
  • Short transformations on my own generic types. Map on a page or a result type reads well. I’m not going to build a streams library out of it, though. A for loop is still the clearest way to filter and sum a slice.
  • Interfaces stay non-generic. If something with a generic method needs a test double, the double goes behind a small interface with concrete methods, such as GetUser(ctx, id) (User, error), and the real implementation calls GetJSON[User] inside it.

Go 1.27 also generalizes function type inference to every assignment context, so a generic function can be assigned or converted to a matching function type without spelling out its type arguments. It’s a small change that removes another set of brackets.

The takeaway

Generic methods won’t change how most Go code looks, and that’s a good thing. They take a handful of awkward top-level functions and put them back on the types they belong to. Just keep the rule in mind before you design around them: a generic method is a convenience on a concrete type, never part of an interface’s contract.