Skip to content

How to Use Generic Methods in Go 1.27

Go 1.27 adds generic methods, allowing methods to declare their own type parameters. This tutorial shows the syntax, type inference, chaining, testing, and interface limitations.

How to Use Generic Methods in Go 1.27

On this page

Go 1.27 finally lets methods declare their own type parameters, removing a limitation that had forced developers to move some operations into package-level generic functions. The feature became part of Go 1.27, released on August 19, 2026, and the Go 1.27.1 maintenance release followed on September 1. This tutorial shows how to write generic methods, chain them, understand type inference, and avoid the interface limitation that still applies.

Start with Go 1.27 or later

Generic methods are a language feature introduced in Go 1.27, so an older compiler will reject their syntax. The current 1.27 release line also includes Go 1.27.1, which contains fixes across the compiler, runtime, and several standard-library packages. Check the compiler before changing an existing project.

go version

The result should report Go 1.27 or a later release. If you use goenv, a system package manager, or another version manager, make sure the command used by your project resolves to the intended compiler rather than an older installation elsewhere on your machine.

Set the module to Go 1.27

Open the project's go.mod file and make sure its Go directive targets Go 1.27 or later. This directive tells the Go toolchain which language and module behaviour the project expects.

module example.com/genericmethods

go 1.27

If you are starting a small test project, create it first and initialise the module:

mkdir genericmethods
cd genericmethods
go mod init example.com/genericmethods

Then create a file named main.go. Keeping the experiment in a small module makes it easier to distinguish a language error from a problem in a larger application.

See why generic methods were needed

Go has supported generic types and generic functions since Go 1.18, but methods could not introduce their own type parameters. Consider a generic list whose elements have type E. Before Go 1.27, a transformation that changed E into another type had to be expressed as a package-level generic function.

type List[E any] []E

func MapList[E, R any](list List[E], convert func(E) R) List[R] {
result := make(List[R], len(list))

```
for i, value := range list {
    result[i] = convert(value)
}

return result
```

}

This works, but the operation is separated from the type it operates on. It also makes a chain of transformations read from the inside outward, because every call must pass the previous result into another function. Go 1.27 lets the second type parameter belong directly to a method, which makes the same operation easier to organise and chain.

Write your first generic method

A generic method can use type parameters declared by its receiver and introduce additional type parameters of its own. Here, E belongs to List, while R belongs specifically to Map.

type List[E any] []E

func (list List[E]) Map[R any](convert func(E) R) List[R] {
result := make(List[R], len(list))

```
for i, value := range list {
    result[i] = convert(value)
}

return result
```

}

The receiver List[E] tells Go what type of values already exist in the list. The method parameter R represents the new element type produced by the conversion function. Because the method returns List[R], the result can have a completely different element type from the original list.

Call the method without specifying every type

Go can infer the method's type argument from the conversion function you pass to it. That means a caller normally does not need to write Map[int] or another explicit type argument.

package main

import "fmt"

type List[E any] []E

func (list List[E]) Map[R any](convert func(E) R) List[R] {
result := make(List[R], len(list))

```
for i, value := range list {
    result[i] = convert(value)
}

return result
```

}

func main() {
numbers := List[int]{1, 2, 3}

```
doubled := numbers.Map(func(value int) int {
    return value * 2
})

fmt.Println(doubled)
```

}

The compiler knows that E is int because the receiver is List[int]. It can also determine that R is int from the conversion function's return type. The program therefore produces [2 4 6] without requiring explicit type arguments at the call site.

Use a different result type

The real benefit becomes clearer when the method changes the element type. For example, a list of integers can be converted into a list of strings with the same method.

package main

import (
"fmt"
"strconv"
)

type List[E any] []E

func (list List[E]) Map[R any](convert func(E) R) List[R] {
result := make(List[R], len(list))

```
for i, value := range list {
    result[i] = convert(value)
}

return result
```

}

func main() {
numbers := List[int]{10, 20, 30}

```
labels := numbers.Map(func(value int) string {
    return "item-" + strconv.Itoa(value)
})

fmt.Println(labels)
```

}

Here the receiver contains int, but the resulting list contains string. The method's independent type parameter is what makes that possible. This is different from merely adding a generic receiver type parameter, because the receiver's type is already fixed when the method is called.

Chain generic methods from left to right

Generic methods become particularly useful when several transformations belong to the same data type. Instead of nesting package-level function calls, each method can return another typed value on which the next method operates.

package main

import (
"fmt"
"strconv"
)

type List[E any] []E

func (list List[E]) Map[R any](convert func(E) R) List[R] {
result := make(List[R], len(list))

```
for i, value := range list {
    result[i] = convert(value)
}

return result
```

}

func main() {
numbers := List[int]{1, 2, 3}

```
result := numbers.
    Map(func(value int) int {
        return value * 2
    }).
    Map(func(value int) string {
        return "value=" + strconv.Itoa(value)
    })

fmt.Println(result)
```

}

The first Map returns List[int], so the second method receives integers. The second Map returns List[string]. This left-to-right structure is one of the practical improvements highlighted by the Go team: the operation stays attached to the type while the compiler tracks the changing element type.

Know the interface restriction before designing APIs

There is a significant boundary to generic methods: Go interfaces still cannot declare methods with their own type parameters. A concrete type can have a generic method, but that method cannot satisfy an interface method requiring a generic method because such an interface method cannot currently be declared.

type Processor interface {
Convert[T any](value T) T
}

The declaration above is not valid Go. This means generic methods should not be introduced into an API with the expectation that they will participate directly in interface dispatch. If an operation needs to work through an interface, a package-level generic function or a differently designed interface is often the better choice.

Choose between a generic method and a generic function

Use a generic method when the operation naturally belongs to a concrete type and benefits from method chaining. A collection's transformation, a builder's conversion, or another operation that is conceptually part of the value's API can be a good fit. Use a package-level generic function when the operation needs to work across unrelated types or must integrate with an interface-based design.

DesignBest fit
Generic methodAn operation naturally associated with a concrete generic type
Generic functionAn operation shared across unrelated types or used with interface-based APIs
Interface methodBehaviour that must be dispatched through an interface

The new syntax should therefore reduce unnecessary package-level helpers, not replace every generic function in an existing codebase. The design question is still about ownership: which type should conceptually own the operation, and does the operation need interface compatibility?

Compare the new method with the old workaround

The difference is easiest to see with the same transformation expressed both ways. Before generic methods, the package-level form could look like MapList(MapList(list, first), second), which nests the later operation around the earlier one. With Go 1.27, the method form can read as list.Map(first).Map(second).

// Generic function
result := MapList(
MapList(numbers, double),
format,
)

// Generic method
result := numbers.
Map(double).
Map(format)

Neither form changes the underlying type system or automatically makes a program faster. The advantage is primarily API organisation and readability. The Go team's own example uses this same distinction to show why generic methods are useful for keeping operations in the namespace of the type they work on.

Test the method with go test before integrating it

Once the method compiles, add a test that verifies both the transformation and the resulting type behaviour. A small test gives you a stable check while you refactor the implementation later.

package main

import (
"strconv"
"testing"
)

func TestMap(t *testing.T) {
numbers := List[int]{1, 2, 3}

```
result := numbers.Map(func(value int) string {
    return strconv.Itoa(value * 10)
})

expected := List[string]{"10", "20", "30"}

for i := range expected {
    if result[i] != expected[i] {
        t.Fatalf("result[%d] = %q, want %q",
            i, result[i], expected[i])
    }
}
```

}

Run the test with the same Go version that will build the project:

go test ./...

A successful test confirms that the compiler accepts the generic method and that the runtime result matches the intended transformation. For a library, also test the public API from an external package so that accidental dependence on internal implementation details does not slip into the design.

Check compatibility before adopting the feature

Generic methods are a source-level compatibility boundary because code using the new syntax requires a Go 1.27-capable toolchain. The Go project maintains compatibility across releases, but compatibility with older compilers is a separate concern for library authors. If your library promises support for older Go versions, moving an exported API to generic methods can therefore require a version strategy rather than a simple syntax change.

For a new Go 1.27 application, the decision is simpler: declare the appropriate Go version in go.mod, use generic methods where they make the API clearer, and keep interface requirements in view during design. For a shared library, first identify the minimum supported compiler and decide whether a package-level generic function should remain available. That small compatibility check can prevent a language upgrade from becoming an accidental breaking change.

M

Written by

M. Rizwan Mirza

I’m M. Rizwan Mirza, a Full Stack Developer with over 12 years of experience in web development and software solutions. I work with modern web technologies and enjoy building practical, reliable, and user-friendly digital solutions. I’m also part of TechWare House, where I work on web development projects and technology solutions. One of my favorite websites is TheQuranic.com. Through WizTechnoz, I share my knowledge, experience, tutorials, and useful insights about technology.

44 posts published

All posts by this author

0 Comments

No comments yet. Be the first to share your thoughts.

Join the conversation

Log in or create a free account to leave a comment. You can edit or delete your own comments any time.