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

  1. Profile & identify: use pprof, benchstat, go test –bench –benchmem.
  2. Refactor with generics: replace any/interface usages as type with generics; re-benchmark.
  3. Use generics for containers: e.g. Stack[T any], Map, Filter.
  4. Keep interfaces: for middleware, handlers & plugins where runtime flexibility matters.
  5. 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.