Generic Functions — Find the Bug¶
Table of Contents¶
Introduction¶
15+ buggy generic functions. For each: 1. Read the code 2. Predict what goes wrong 3. Read the Hint 4. Then the Fix with explanation
Bugs cover: missing constraints, wrong type-parameter usage, incorrect instantiation, type-inference failures, surprising runtime behavior.
How to Use This Page¶
- Try compiling each snippet in your head before reading the hint.
- Try the fix yourself before checking the solution.
- Many bugs reproduce in real life: the same patterns appear on Stack Overflow weekly.
Bugs¶
Bug 1 — Missing constraint for +¶
Hint
What does `any` allow you to do with `T`?Fix
`any` does not permit the `+` operator. Add a numeric constraint: **Explanation:** Operations on a type parameter are allowed only when **every** type in the constraint's type set permits them. With `any`, the type set is "all types," and `+` is not defined for all types (e.g. `bool`).Bug 2 — Inference fails for return-only T¶
Hint
What types does the call site provide to the compiler?Fix
**Explanation:** When `T` appears only in the return type and not in any argument, type inference cannot deduce it. You must instantiate explicitly.Bug 3 — Method with its own type parameter¶
type Box[T any] struct{ V T }
func (b Box[T]) MapTo[U any](f func(T) U) Box[U] {
return Box[U]{V: f(b.V)}
}
Hint
What restriction does Go impose on methods?Fix
Move the second type parameter to a free function: **Explanation:** Methods cannot introduce type parameters of their own; they inherit only the receiver type's parameters. The compiler error is: *"method must have no type parameters"*.Bug 4 — ~ token forgotten for defined types¶
type Number interface { int | float64 }
func Double[T Number](x T) T { return x * 2 }
type Cents int
var c Cents = 50
Double(c) // compile error
Hint
What is the underlying type of `Cents`?Fix
**Explanation:** Without the `~` token, only the literal types `int` and `float64` are allowed. `Cents` has underlying type `int` but is not literally `int`. The `~` admits any defined type with the matching underlying.Bug 5 — Mixed untyped constant inference¶
func Min[T cmp.Ordered](a, b T) T {
if a < b { return a }
return b
}
m := Min(1, 2.0) // compile error
Hint
What types do `1` and `2.0` default to?Fix
Either convert one operand or be explicit: **Explanation:** `1` defaults to `int`, `2.0` defaults to `float64`. The compiler can't unify them into a single `T`.Bug 6 — Comparing slice values¶
Hint
Is `==` defined for slices?Fix
Use the `comparable` constraint: For slices specifically you can't compare with `==` — use `slices.Equal[T comparable](a, b []T) bool` from the `slices` package, or write a deep-compare yourself. **Explanation:** `any` permits any type, but `==` requires `comparable`. Slices are not comparable.Bug 7 — Unused type parameter¶
Hint
Why is `U` declared?Fix
Remove `U`: **Explanation:** Unused type parameters add cognitive load and force callers to specify or infer them unnecessarily. Drop them.Bug 8 — Forgetting that T(int) requires constraint¶
Hint
What does `int(x)` require?Fix
Constrain `T` so the conversion is valid: **Explanation:** `int(x)` is only valid when `T`'s type set contains types convertible to `int`. With `any`, this includes types like `string` for which the conversion fails.Bug 9 — fmt.Stringer constraint and value vs pointer receiver¶
type Greeter interface {
Hello() string
}
type EnglishGreeter struct{ Name string }
func (g *EnglishGreeter) Hello() string { return "Hello, " + g.Name }
func Greet[T Greeter](g T) string { return g.Hello() }
g := EnglishGreeter{Name: "Ada"}
Greet(g) // compile error
Hint
What is the method set of `EnglishGreeter`?Fix
Pass a pointer: **Explanation:** `Hello()` is declared with a pointer receiver, so the method set of `EnglishGreeter` (value type) does NOT include it. `*EnglishGreeter` does. Generics use the same method set rules as interfaces.Bug 10 — Using interface{} instead of any¶
Hint
It compiles — but does it follow modern style?Fix
**Explanation:** `any` and `interface{}` are aliases — but `any` is the modern, idiomatic form. Keep style consistent.Bug 11 — Range over a generic-typed map¶
func KeysSorted[K, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
return keys
}
Hint
What constraints does `map[K]V` require? What does `<` need?Fix
**Explanation:** Map keys must be `comparable`, but the function uses `<`. `cmp.Ordered` is the right constraint — it implies `comparable` and adds ordering.Bug 12 — Closure capture of loop variable¶
func MakeAdders[T int | float64](xs []T) []func(T) T {
var fs []func(T) T
for _, x := range xs {
fs = append(fs, func(y T) T { return x + y })
}
return fs
}
// Pre-Go 1.22: all closures return the last x + y
Hint
In pre-1.22 Go, what is the lifetime of `x` in a `for ... range`?Fix
For Go versions < 1.22, shadow `x` inside the loop: In Go 1.22+, the loop variable is per-iteration and the original code works correctly. **Explanation:** This is not generics-specific — but it bites generics users because `MakeAdders` looks like a useful helper and the bug is invisible until called.Bug 13 — Empty type set¶
Hint
Can a single type be both `int` and `string`?Fix
Use `|` instead of `;`: **Explanation:** Inside an interface, `;` separates **independent** elements that all must hold. The intersection of `int` and `string` is empty, so no type can satisfy `Weird`. The compiler permits the declaration but every call site fails.Bug 14 — Pointer-of-zero gotcha¶
Hint
This compiles — but what's the bug if `T` is itself a pointer?Fix
If `T = *Foo`, the zero value is `(*Foo)(nil)`, which the function returns. That's fine, but the caller may be confused. Document the behavior or refactor: **Explanation:** Not strictly a bug — but a subtlety to remember. `var z T` always returns the zero value of T, even when T is a pointer.Bug 15 — Unintended copy of large struct¶
func MaxBy[T any, K cmp.Ordered](xs []T, key func(T) K) (T, bool) {
if len(xs) == 0 { var z T; return z, false }
best := xs[0]
for _, x := range xs[1:] {
if key(x) > key(best) {
best = x
}
}
return best, true
}
Hint
If `T` is a struct of 1KB, what is the cost of `best := xs[0]` and `best = x`?Fix
For very large `T`, work with indices: **Explanation:** Generic functions copy values just like ordinary functions. Large struct types incur copy costs at every assignment. For tiny types, the original code is fine.Bug 16 — Missing comparable for set semantics¶
func ToSet[T any](xs []T) map[T]struct{} {
out := make(map[T]struct{}, len(xs))
for _, x := range xs {
out[x] = struct{}{}
}
return out
}
Hint
What does `map[T]struct{}` require of `T`?Fix
**Explanation:** Map keys must satisfy the `comparable` constraint. `any` is too loose.Bug 17 — Calling a method that doesn't exist on T¶
func Stringify[T any](xs []T) []string {
out := make([]string, len(xs))
for i, x := range xs {
out[i] = x.String() // compile error
}
return out
}
Hint
Does `any` guarantee a `String()` method?Fix
**Explanation:** A method may be called on a type parameter only when the constraint guarantees the method. `any` does not.Bug 18 — ~T confusion with named pointer types¶
type MyInt int
type MyIntPtr *MyInt
type IntLike interface { ~int }
var p MyIntPtr
F(p) // compile error if F expects IntLike
Hint
What is the underlying type of `MyIntPtr`?Fix
The underlying type of `MyIntPtr` is `*MyInt`, not `int`. `~int` doesn't accept it. Pass `*p`: **Explanation:** Approximation matches **underlying** types. Pointer types have pointer underlyings, not the type pointed to.Bug 19 — Forgetting context cancellation in ParallelMap¶
func ParallelMap[T, U any](xs []T, f func(T) U) []U {
out := make([]U, len(xs))
var wg sync.WaitGroup
for i, x := range xs {
wg.Add(1)
go func(i int, x T) {
defer wg.Done()
out[i] = f(x)
}(i, x)
}
wg.Wait()
return out
}
Hint
What if you have a million elements?Fix
Add `context.Context` and bounded concurrency: **Explanation:** Unbounded goroutines is a denial-of-service waiting to happen. Even small generic helpers must respect resource limits.Bug 20 — Using == to compare typed function values¶
Hint
Are functions comparable in Go?Fix
Restrict to `comparable`: Or use `reflect.DeepEqual` for the rare cases where you actually need it. **Explanation:** Functions are not comparable. `comparable` excludes them; `any` permits the type but disallows the operator at compile time.Cheat Sheet of Failure Modes¶
| Symptom | Cause | Fix |
|---|---|---|
cannot use ... as T (T does not satisfy ...) | Constraint missing or too tight | Loosen with ~T or extend the union |
invalid operation: x op y (operator not defined for T) | Operation not in type set | Tighten constraint |
cannot infer T | T appears only in return | Instantiate explicitly |
cannot infer T (mismatched types int and float64) | Untyped constants disagree | Convert one operand |
methods must have no type parameters | Method declares its own | Use a free function |
interface contains type constraints | Constraint used as runtime interface | Don't use as variable type |
... is not in interface | Method receiver pointer/value mismatch | Pass &v or change receiver |
Summary¶
The most common generic-function bugs cluster around three themes: constraints too loose (operators not defined), inference failure (return-only type parameter), and method-set surprises (pointer vs value receiver). Memorize these patterns and most everyday issues become quick to diagnose.