Stage 1 · Code
Generics
When Not to Use Generics
Learn the real tradeoffs of generics so you can use them deliberately: clearer reuse when they help, and simpler non-generic code when they do not.
Generics Are Not an Upgrade Button
Once people learn a new tool, the next temptation is to spread it everywhere. That is normal. But generics are not an automatic 'better version' of ordinary Go code. They are a specific answer to a specific problem: one stable algorithm or structure that truly needs to work across multiple concrete types.
If you only have one meaningful concrete type, or if the generic version makes the code harder to understand than the repeated version, the generic version is often the worse design even if it feels more advanced.
A good rule is: use generics when they remove real duplication from a real reusable abstraction. Do not use them just to make code look more sophisticated.
Readability and Discoverability
Sometimes a generic API hides the story instead of clarifying it. A reader who sees Process[T, U, K] has to decode the type parameters, the constraints, and the relationships between them before they understand the job being done. A plain SaveUserProfile(profile UserProfile) error may be much easier to read if that is the only job your code actually needs.
| Question | Generic may help | Non-generic may be clearer |
|---|---|---|
| Is the algorithm identical across many types? | Yes | No |
| Will several concrete types really use this soon? | Yes | Probably not |
| Does the generic name still explain the domain clearly? | Yes | No |
Performance Honesty
Another trap is assuming generics are free. They are not. In Go, generic code is implemented with a mix of techniques rather than a single magical strategy. Roughly speaking, the compiler may share code for some shapes and pass extra type information around, and it may specialize some cases too. People sometimes describe this as a blend of dictionary-passing and partial monomorphization.
You do not need to memorize the compiler internals. The practical lesson is simpler: do not promise yourself automatic performance wins just because you used generics. Measure if performance matters. Sometimes generic code is great. Sometimes an interface-based design or a concrete type is just as good or clearer.
Generics are mainly a maintainability and expressiveness feature. They can perform well, but 'generic' does not automatically mean 'faster' or 'zero-cost.'
Sometimes an Interface Is Clearer
If your code cares about behavior rather than stored value type, an interface is often the clearer tool. For example, if a function only needs to call String() on each value, an interface communicates that need directly. A generic function can express it too, but the interface version may be easier to discover and easier to discuss.
That does not mean a generic version would be wrong. It means the generic version would not automatically be better. The interface says, in one word, what capability the function expects: Report().
A Real Language Limit
There is also an important current language limit to know. A method on a generic type can use the receiver's type parameters, but the method cannot introduce brand new type parameters of its own.
Three Decision Scenarios
Let's reason through a few real 'should I use generics here?' situations out loud.
Reverse[T any]([]T) []T— yes, this is a good generic candidate. The algorithm is identical whether the slice holds ints, strings, or structs.SendWelcomeEmail(user User)— no, keep this concrete. The code is about one domain concept, not a reusable cross-type algorithm.PrintReports(items []Reporter)— probably use an interface. The key shared idea is behavior (Report()), not storage of arbitrary values.
That is the deeper decision-making rule: ask what is actually stable. If the stable thing is a data-structure shape or algorithm over many concrete types, generics fit well. If the stable thing is a capability or domain behavior, interfaces or concrete functions are often simpler.
Quiz
What is the healthiest default attitude toward generics?
Practice
This last exercise intentionally avoids generics. The point is to practice choosing the simpler tool when the problem is really about behavior.
Given the `Notifier` interface below, write `SendAll(notifiers []Notifier) int` so it calls `Notify()` on each item and returns the number of notifications sent. This is a case where an interface-based function is clearer than a generic one.
Summary
- Generics are powerful, but they are not an automatic improvement over ordinary Go code.
- Overusing generics can hurt readability and make APIs harder to discover.
- Generic code is not automatically free from a performance perspective, so do not overclaim — measure when it matters.
- Interfaces are often clearer when the real shared idea is behavior rather than a reusable cross-type algorithm or container.
- Methods on generic types can use the receiver's type parameters, but they cannot declare brand new type parameters of their own.
- The best design question is always: what is actually stable here — the algorithm, the behavior, or the domain type?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.