Go generics in practice: what they're actually good for
Go generics landed in 1.18 and the community spent about six months either overclaiming or dismissing them. The reality landed somewhere quieter: they're useful in a specific set of situations, and actively harmful in others.
A year and a half into using them in production, here's what I've actually found them good for.
The Clear Win: Container Types
The canonical use case is also the best one. If you're writing a data structure that holds or operates on values, queues, stacks, sets, ring buffers, ordered maps: generics are unambiguously the right tool. Before 1.18 you either used interface{} and paid the type assertion tax everywhere, or you wrote a separate implementation per type.
Our ring buffer from an earlier post is a good example. The generic version is cleaner, type-safe, and carries zero runtime overhead compared to the interface{} version:
// Before generics
type RingBuffer struct {
buffer []interface{}
// ...
}
// Every read: val := buf.Get().(MyType)
// With generics
type RingBuffer[T any] struct {
buffer []T
// ...
}
// Every read: val := buf.Get() — already MyTypeNo allocations from boxing, no panics from wrong type assertions. This is generics doing exactly what they should.
Useful But Requires Restraint: Functional Helpers
Map, filter, reduce, the functional trio: are finally expressible without reflection or code generation. This is genuinely nice for one-off transformations on slices:
func Map[T, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}The danger here is overuse. Go's explicit loops are readable and fast. Wrapping everything in Map/Filter adds indirection without always adding clarity. I use these helpers when the transformation is genuinely complex or reused widely, not as a default style.
Where Generics Make Things Worse
The most common mistake I've seen is reaching for generics when interfaces are the right tool. If you need a function that accepts different types and does different things with them based on their methods, that's an interface. Generics constrain at compile time; interfaces dispatch at runtime. They solve different problems.
Deeply nested generic constraints also become unreadable fast. If you find yourself writing type parameters with four or five constraints chained together, it's usually a sign the abstraction is wrong, not that you need more type parameters.
The Bottom Line
Generics in Go are well-suited to one thing: writing code that works identically across types without runtime overhead. Container types and type-safe utility functions are the sweet spot. If you're reaching for them for anything more complex, stop and ask whether an interface solves the problem more clearly. In Go, clarity usually wins.