
Go Generics
- 665 installs
- 137 repo stars
- Updated June 20, 2026
- cxuu/golang-skills
go-generics is a Claude Code skill that provides precise guidance on when and how to apply Go generics, constraints, and type parameters for developers writing reusable Go 1.18+ backend and utility code.
About
go-generics from cxuu/golang-skills helps developers decide when to use Go generics versus concrete types or interfaces when writing generic functions, types, and constraints in Go 1.18 or later. The skill routes constraint composition questions to references/CONSTRAINTS.md and advises starting with concrete types before generalizing only when multiple types share real behavior. Developers reach for go-generics when choosing constraints, comparing type aliases to type definitions, or writing utility functions that could work across multiple Go types even if generics are not explicitly mentioned. Interface design without generics is explicitly deferred to the go-interfaces sibling skill.
- Decision flow that starts with concrete types and generalizes only on second similar type
- Clear rules for when to prefer generics versus interfaces or any+type switches
- References CONSTRAINTS.md for type sets and constraint composition
- Compatibility note that generics require Go 1.18+
- Anti-pattern guardrails that stop premature abstraction
Go Generics by the numbers
- 665 all-time installs (skills.sh)
- Ranked #556 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cxuu/golang-skills --skill go-genericsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 665 |
|---|---|
| repo stars | ★ 137 |
| Last updated | June 20, 2026 |
| Repository | cxuu/golang-skills ↗ |
When should Go code use generics?
Get precise guidance on when and how to apply Go generics instead of concrete types or interfaces.
Who is it for?
Go developers on Go 1.18+ writing reusable utility functions or shared data structures who need constraint and type-parameter guidance.
Skip if: Teams on Go versions below 1.18 or developers needing interface-only design patterns without type parameters.
When should I use this skill?
The user writes Go utility functions, asks about generics, constraints, type aliases, or type definitions in Go code.
What you get
Go generic function or type implementation with appropriate constraints, or a concrete-type recommendation with rationale.
- Generic Go function or type implementation
- Constraint composition guidance
Files
Go Generics and Type Parameters
Compatibility: Generics require Go 1.18+.
Resource Routing
references/CONSTRAINTS.md- Read when composing constraints, using type sets, or choosing between generics and interfaces.
When to Use Generics
Start with concrete types. Generalize only when a second type appears.
Prefer Generics When
- Multiple types share identical logic (sorting, filtering, map/reduce)
- You would otherwise rely on
anyand excessive type switching - You are building a reusable data structure (concurrent-safe set, ordered map)
Avoid Generics When
- Only one type is being instantiated in practice
- Interfaces already model the shared behavior cleanly
- The generic code is harder to read than the type-specific alternative
"Write code, don't design types." — Robert Griesemer and Ian Lance Taylor
Decision Flow
Do multiple types share identical logic?
├─ No → Use concrete types
├─ Yes → Do they share a useful interface?
│ ├─ Yes → Use an interface
│ └─ No → Use genericsBad:
// Premature generics: only ever called with int
func Sum[T constraints.Integer | constraints.Float](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}Good:
func SumInts(vals []int) int {
var total int
for _, v := range vals {
total += v
}
return total
}---
Type Parameter Naming
| Name | Typical Use |
|---|---|
T | General type parameter |
K | Map key type |
V | Map value type |
E | Element/item type |
For complex constraints, a short descriptive name is acceptable:
func Marshal[Opts encoding.MarshalOptions](v any, opts Opts) ([]byte, error)---
Type Aliases vs Type Definitions
Type aliases (type Old = new.Name) are rare — use only for package migration or gradual API refactoring.
---
Constraint Composition
Combine constraints with ~ (underlying type) and | (union):
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Numeric](vals []T) T {
var total T
for _, v := range vals {
total += v
}
return total
}Use the constraints package or cmp package (Go 1.21+) for standard constraints like cmp.Ordered instead of writing your own.
---
Common Pitfalls
Don't Wrap Standard Library Types
// Bad: generic wrapper adds complexity without value
type Set[T comparable] struct {
m map[T]struct{}
}
// Better: use map[T]struct{} directly when the usage is simple
seen := map[string]struct{}{}Generics justify their complexity when they eliminate duplication across multiple call sites. A single-use generic is just indirection.
Don't Use Generics for Interface Satisfaction
// Bad: T is only used to satisfy an interface — just use the interface
func Process[T io.Reader](r T) error { ... }
// Good: accept the interface directly
func Process(r io.Reader) error { ... }Avoid Over-Constraining
// Bad: constraint is more restrictive than needed
func Contains[T interface{ ~int | ~string }](slice []T, target T) bool { ... }
// Good: comparable is sufficient
func Contains[T comparable](slice []T, target T) bool { ... }---
Quick Reference
| Topic | Guidance |
|---|---|
| When to use generics | Only when multiple types share identical logic and interfaces don't suffice |
| Starting point | Write concrete code first; generalize later |
| Naming | Single uppercase letter (T, K, V, E) |
| Type aliases | Same type, alternate name; use only for migration |
| Constraint composition | Use ~ for underlying types, ` |
| Common pitfall | Don't genericize single-use code or when interfaces suffice |
---
Related Skills
- Interfaces vs generics: See go-interfaces when deciding whether an interface already models the shared behavior without generics
- Type declarations: See go-declarations when defining new types, type aliases, or choosing between type definitions and aliases
- Documenting generic APIs: See go-documentation when writing doc comments and runnable examples for generic functions
- Naming type parameters: See go-naming when choosing names for type parameters or constraint interfaces
Type Constraints in Go Generics
Sources: Google Go Style Guide, Go language specification
Constraints define what operations a type parameter supports. Choose the narrowest constraint that satisfies your function's needs — no more.
---
Built-in Constraints
Normative: Use standard constraints before writing your own.
| Constraint | Meaning |
|---|---|
any | Alias for interface{}; no requirements on the type |
comparable | Supports == and !=; required for map keys |
cmp.Ordered | Supports <, <=, >=, > (Go 1.21+, replaces constraints.Ordered) |
Prefer cmp.Ordered (from cmp package) over the deprecated golang.org/x/exp/constraints.Ordered for new code.
---
The ~ Operator (Underlying Types)
Advisory: Use ~ when you want to accept named types built on aprimitive.
The ~T syntax matches any type whose underlying type is T. Without ~, only the exact type matches.
type Celsius float64
type ExactFloat interface{ float64 } // rejects Celsius
type AnyFloat64 interface{ ~float64 } // accepts CelsiusUse ~ when callers are likely to define named types over the base type. Omit ~ only when you need to restrict to the exact built-in type.
---
Composing and Writing Constraints
Advisory: Define a custom constraint only when no standard one fits.
Combine types with | and embed constraints to compose them:
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
type Addable interface {
Numeric | ~string // numbers and string concatenation
}Constraints can require methods alongside type elements:
type Stringer interface {
comparable
String() string
}A type satisfying Stringer must be comparable and have a String() method.
---
Avoiding Over-Constraining
Normative: Use the minimal constraint that supports the operations
you perform.
Bad
// Only uses == but restricts to int and string
func Contains[T interface{ ~int | ~string }](s []T, v T) bool { ... }Good
// comparable is the minimal constraint for ==
func Contains[T comparable](s []T, v T) bool { ... }Over-constraining limits reuse and forces callers to work around restrictions that the implementation never needed.
Type Inference
Advisory: Let the compiler infer type arguments when unambiguous.
The compiler infers type parameters from function arguments:
result := slices.Contains[string](names, "alice") // explicit — unnecessary
result := slices.Contains(names, "alice") // inferred — preferredSupply type arguments explicitly only when there are no function arguments to infer from, the inferred type is wrong (e.g., untyped constant promotes to the wrong type), or readability benefits from making the type visible.
---
Common Pitfalls
Don't Use Generics When Interfaces Suffice
Normative: From Google Style Guide — prefer interfaces when types
share a useful unifying interface.
Bad
// T is only used to satisfy io.Reader — just use the interface
func Process[T io.Reader](r T) error { ... }Good
func Process(r io.Reader) error { ... }If the constraint is a single existing interface, accept the interface directly.
Don't Wrap Standard Library Types Generically
Advisory: A single-use generic is just indirection.
Bad
type Set[T comparable] struct{ m map[T]struct{} } // only ever Set[string]Good
seen := map[string]struct{}{} // use map directly for a single instantiationGenerics justify complexity when they eliminate duplication across multiple call sites. If only one type is ever used, start concrete.
Method Sets and Type Constraints
You can only call operations the constraint allows:
Bad
func Stringify[T any](v T) string {
return v.String() // compile error: any does not have String()
}Good
func Stringify[T fmt.Stringer](v T) string {
return v.String()
}---
Quick Reference
| Topic | Guidance |
|---|---|
| Default constraint | any — use when no operations on T are needed |
| Equality checks | comparable — required for ==, !=, and map keys |
| Ordering | cmp.Ordered (Go 1.21+) for <, > comparisons |
| Named types | Use ~T to accept types whose underlying type is T |
| Union types | Combine with `\ |
| Custom constraints | Define as interface with type elements and/or methods |
| Type inference | Omit type args when the compiler can infer them |
| Minimal constraint | Use the narrowest constraint the function actually needs |
Related skills
How it compares
Choose go-generics for type-parameter decisions in Go 1.18+ rather than interface-only abstraction patterns covered by go-interfaces.
FAQ
What Go version does go-generics require?
go-generics requires Go 1.18 or later because type parameters and generics were introduced in that release. The skill guides generic functions, constraints, and type alias decisions for backend and utility Go code.
When does go-generics recommend using generics?
go-generics advises starting with concrete types and generalizing only when multiple types genuinely share behavior. The skill helps compose constraints using references/CONSTRAINTS.md and defers interface-only patterns to go-interfaces.