For a while my honest answer to "do you use Go generics?" was no, and I half meant it as a boast. They landed in 1.18, the language survived a decade without them, and most of the time interface{} and a little copy-paste got the job done. Then I wrote the same Map, Filter and Keys helpers for the fourth project in a row, and the boast started to sound like stubbornness.
So here's where I've landed, which is firmly "sometimes".
The place generics actually pay off for me is small, self-contained container and collection code:
func Keys[K comparable, V any](m map[K]V) []K {
out := make([]K, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
That's it. One function, one obvious meaning, works for every map I'll ever have, and crucially the type parameters disappear at the call site: Keys(cfg) just works. No .(string) assertions, no runtime panic waiting for a type I forgot about. The same goes for a generic Set[T comparable], a Result[T] type, the odd numeric helper. Tight, boring, reusable plumbing.
Where I still don't reach for them is business logic. The moment a generic function grows two type parameters and a constraint interface with three methods, I've usually built an abstraction that's harder to read than the duplication it replaced. Go's whole appeal is that you can read a function and know what it does. A wall of [T Ordered, K comparable] quietly trades that away.
So the answer to "do I even need them" is: rarely, and that's fine. They're a sharp tool for a narrow job. I use them for collections and a handful of utilities, I keep them out of the domain, and the code stays the kind of dull that I can still understand at 2am during an incident. That was always the point of Go.