Go 1.18’s generics can deliver dramatic performance gains over interface{}-based code, especially in hot paths. Here’s a super-concise example.
1. Benchmark Snapshot
Let’s take those two types below
Non-generic JWK:
type JWK struct {
PrivateKey any `json:"-"`
PublicKey any `json:"public_key"`
Alg string `json:"alg"`
Kid string `json:"kid"`
}
Generic JWK:
type PrivateKeyPair interface {
ed25519.PrivateKey | *rsa.PrivateKey
}
type PublicKeyPair interface {
ed25519.PublicKey | *rsa.PublicKey
}
type JWK[PRV PrivateKeyPair, PUB PublicKeyPair] struct {
PrivateKey PRV `json:"-"`
PublicKey PUB `json:"public_key"`
Alg string
Kid string
}
Valid-token path:
• any: ~90 µs, 66 allocs (~4.6 KB)
• Generics: ~1.8 µs, 3 allocs (~200 B)
≈ 50× faster & 23× less memory.
2. Why Generics Win
- No boxing: concrete types replace
interface{}, eliminating heap allocations & type assertions. - Inlining & specialization: compiler generates optimized code per type.
3. When to Use Interfaces
My basic rule of thumb is to avoid interface{} as types.
4. Practical Guidance
- Profile & identify: use
pprof,benchstat,go test –bench –benchmem. - Refactor with generics: replace
any/interface usages as type with generics; re-benchmark. - Use generics for containers: e.g.
Stack[T any],Map,Filter. - Keep interfaces: for middleware, handlers & plugins where runtime flexibility matters.
- Watch code size: limit instantiations if binary size is a concern.
5. Conclusion & Next Steps
- Generics = performance + type safety.
- Interfaces = runtime flexibility.
- Action: profile hotspots, refactor with generics, re-benchmark, use interfaces where needed.
Balancing generics for speed and interfaces for flexibility leads to efficient, maintainable Go code.