Generics landed in Go a good while back now, and the dust has well and truly settled. The early hot takes have faded, nobody is rewriting the standard library overnight, and we can look at the feature with clear eyes. So: do I actually need them? Mostly no. Occasionally yes, and when the answer is yes it's a genuine relief.
The honest position is that most Go code does not want generics. The language was deliberately built to make you reach for an interface or just write the second copy of the function, and that advice is still right most of the time. If you have a ProcessUsers and you're tempted to make it Process[T any], stop and ask whether anything other than User will ever go through it. Usually nothing will.
Where they actually pay off
The clear win is the container and collection plumbing you used to write with interface{} and a type assertion at every boundary. A typed set, a generic LRU cache, a Map/Filter/Reduce helper if you're into that sort of thing. The value isn't cleverness, it's that the compiler now catches the mistake you used to find at runtime.
The second win is constraints on numeric code. Before, a function that summed a slice had to pick int or float64 and live with it, or accept interface{} and cry. Now:
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs {
total += x
}
return total
}
That ~ is the part people forget. It means "any type whose underlying type is this", so your type Celsius float64 still satisfies the constraint. Leave it off and you'll wonder why your domain types don't fit.
Where I still don't bother
I do not generic-ify business logic. If a function exists to do one concrete thing to one concrete type, parameterising it adds a type list at the call site and buys nothing. I also avoid the temptation to build a grand generic abstraction "for later". Later rarely comes, and go doc on a heavily-parameterised signature is genuinely harder to read than two honest functions.
The rule I've landed on: use generics when you're describing a relationship between types, not when you're describing a behaviour. Behaviour is what interfaces are for, and they're still the better tool. Containers, constraints, and the odd utility are where generics shine, and that's a perfectly respectable amount of the language to leave them in. You don't need them most days. The days you do, you'll be glad they're there.