Golang-skills go-generics
Use when deciding whether to use Go generics, writing generic functions or types, choosing constraints, or picking between type aliases and type definitions. Also use when a user is writing a utility function that could work with multiple types, even if they don't mention generics explicitly. Does not cover interface design without generics (see go-interfaces).
git clone https://github.com/cxuu/golang-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/cxuu/golang-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/go-generics" ~/.claude/skills/cxuu-golang-skills-go-generics && rm -rf "$T"
skills/go-generics/SKILL.mdGo Generics and Type Parameters
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
and excessive type switchingany - 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 generics
Bad:
// 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 |
|---|---|
| General type parameter |
| Map key type |
| Map value type |
| 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.
Read references/CONSTRAINTS.md when writing custom type constraints, composing constraints with ~ and |, or debugging type inference issues.
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 (, , , ) |
| 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