Generics (Type Parameters)
Added in Go 1.18, generics let you write one function or type that works across many types — without losing Go's type safety. By the end you'll write generic functions with type parameters, constrain them with interfaces, and build a fully generic data structure.
Learn Generics (Type Parameters) in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
📊 Generics Vocabulary
Term
Meaning
[T any]
A type parameter named T, allowing any type
constraint
An interface limiting which types T may be
any
The widest constraint — same as interface{}
comparable
Built-in constraint for types usable with ==
~int
int and any type whose underlying type is int
Type inference usually lets you call a generic function without spelling out the type: Max(3, 7) instead of Max[int](3, 7) .
1️⃣ Generic Functions
A type parameter goes in square brackets after the function name: func Max[T ...](a, b T) T . Inside the function, T stands for whatever concrete type the caller uses. The list int | float64 | string is a quick inline constraint saying which types are allowed — here, the ones > works on.
A function can have several type parameters. Map below takes an input type T and a possibly-different output type U — the staple of functional-style slice transforms.
Your turn. Fill in the constraint that allows any element type so First works on both slices.
2️⃣ Constraints
A constraint is just an interface that lists the permitted types (joined with | ) and/or required methods. Naming it makes intent clear and reusable. The ~ token matters: ~int means "int or any named type whose underlying type is int", so your own type Celsius int still qualifies.
3️⃣ Generic Types
Types can take type parameters too. Stack[T any] is a stack of any element type, and its methods reuse the same T . To produce the zero value of an unknown type inside a method, declare var zero T — that's the idiomatic way to return "nothing" when the type isn't known until instantiation.
🧩 Reorder Challenge
These lines define a generic Contains function and call it. Put them in the correct order:
B, D, C, E, A, F, G. Open the function with a comparable constraint (needed for == ), loop over the slice checking each element, close the loop, return false if nothing matched, close the function, then call it. comparable is required because the body uses == .
🧠 Quick Recall
Predict the output before revealing the answer.
9 — Go infers T = int from the arguments, so no explicit [int] is needed, and 9 > 4.
No. any doesn't guarantee == works (slices, maps, and funcs aren't comparable). Change the constraint to comparable and it compiles.
0 true — var z T is the zero value of whatever T is: 0 for int , and the empty string for string (so the comparison to "" is true).
Pro Tips
- 💡 Don't reach for generics first. If a plain function or an interface already does the job clearly, use that. Generics shine for containers and slice/map utilities.
- 💡 Use comparable when you need == , and a numeric constraint when you need + or < . The constraint must permit every operation the body uses.
- 💡 Lean on type inference. Spell out [int] only when Go can't figure the type out (for example, a generic function with no arguments of type T ).
- 💡 Check the standard library — slices and maps packages already provide generic helpers like slices.Contains and slices.Sort .
Common Errors (and the fix)
- "invalid operation: a == b (incomparable types in type set)" — your constraint is any but you used == . Use comparable instead.
- "T does not support < (operator not defined)" — the constraint doesn't allow ordering. Add a numeric/ordered constraint such as int | float64 | string .
- "cannot infer T" — Go couldn't deduce the type from the arguments. Provide it explicitly: MyFunc[int](...) .
- Forgetting ~ — int in a constraint matches only int exactly; your type ID int won't qualify. Use ~int to include defined types.
- "missing type constraint" — you wrote [T] with no constraint. Every type parameter needs one; use [T any] if anything goes.
Frequently Asked Questions
Q: Generics or interfaces — which should I use?
Use an interface when you want different types to share behaviour behind a contract. Use generics when you want the same logic to operate on many types while keeping the exact type (so the compiler still checks it). Containers and slice utilities are the classic generics case.
A built-in constraint for types that can be compared with == and != — most types, but not slices, maps, or functions. Use it for map keys and equality checks in generic code.
Without ~ , the constraint matches the exact type int only. With ~int , it also matches any type whose underlying type is int , such as type Age int . That makes constraints work with user-defined named types.
Generics are resolved at compile time, so there's no runtime type-checking cost like reflection. Go may share generated code across types in some cases, but for everyday use you can treat generics as zero-overhead abstractions.
Mini-Challenge: Generic Filter
No blanks — just a brief. Write a single Filter that works on any slice type, then use it on both ints and strings. Together with Map from earlier, you now have the building blocks of a functional toolkit.
🎉 Lesson Complete!
- ✅ Type parameters [T any] let one function serve many types
- ✅ Go usually infers the type, so calls stay clean
- ✅ Constraints are interfaces listing allowed types (with | ) or methods
- ✅ ~int matches defined types; comparable enables ==
- ✅ Types can be generic too — like Stack[T] — and var zero T gives the zero value
- ✅ Next lesson: Working with JSON (Marshal/Unmarshal)
Practice quiz
In which Go version were generics added?
- 1.11
- 1.16
- 1.18
- 1.21
Answer: 1.18. Generics arrived in Go 1.18.
What does [T any] declare?
- a type parameter T allowing any type
- a variable named T
- an interface method
- a constant
Answer: a type parameter T allowing any type. [T any] is a type parameter named T that accepts any type.
In func Map[T, U any](s []T, fn func(T) U) []U, T and U are...
- always the same type
- both int
- return values
- input and output types that can differ
Answer: input and output types that can differ. T is the input element type and U the output type; they may be different.
What does fmt.Println(Max(3, 7)) print for a generic Max?
- 3
- 7
- 10
- a compile error
Answer: 7. Go infers T = int and Max returns the larger, 7.
What does a constraint like ~int | ~int64 | ~float64 express?
- the set of allowed types (the | means or)
- a list of methods
- a tuple
- a struct
Answer: the set of allowed types (the | means or). It's a type set; | means 'or', listing the permitted underlying types.
What is the widest constraint, equivalent to interface{}?
- comparable
- Ordered
- any
- Number
Answer: any. any is the widest constraint and is an alias for interface{}.
Which built-in constraint is needed to use == in generic code?
- any
- comparable
- Number
- Stringer
Answer: comparable. comparable permits == and !=; not all types (slices, maps, funcs) qualify.
Inside a generic method, what does var zero T produce?
- nil always
- a panic
- the first element
- the zero value of T
Answer: the zero value of T. var zero T is the zero value of whatever type T is instantiated as.
Why does ~int use a tilde?
- to negate int
- to also match named types whose underlying type is int
- to make it a pointer
- for performance
Answer: to also match named types whose underlying type is int. Without ~ only the exact int matches; ~int also admits type Age int.
Do generics add runtime type-checking overhead like reflection?
- yes, always
- only for slices
- no, they are resolved at compile time
- only with comparable
Answer: no, they are resolved at compile time. Generics are compile-time, so there's no reflection-style runtime cost.