Skip to content

Generic Functions — Find the Bug

Table of Contents

  1. Introduction
  2. How to Use This Page
  3. Bugs
  4. Cheat Sheet of Failure Modes
  5. Summary

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 +

func Sum[T any](xs []T) T {
    var s T
    for _, x := range xs {
        s += x
    }
    return s
}
Hint What does `any` allow you to do with `T`?
Fix `any` does not permit the `+` operator. Add a numeric constraint:
type Numeric interface {
    ~int | ~int64 | ~float32 | ~float64
}

func Sum[T Numeric](xs []T) T {
    var s T
    for _, x := range xs {
        s += x
    }
    return s
}
**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

func Make[T any]() T {
    var z T
    return z
}

x := Make()
fmt.Println(x)
Hint What types does the call site provide to the compiler?
Fix
x := Make[int]()
**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:
func MapBox[T, U any](b Box[T], f func(T) U) Box[U] {
    return Box[U]{V: f(b.V)}
}
**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
type Number interface { ~int | ~float64 }
**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:
m := Min[float64](1, 2.0) // explicit T
m := Min(1.0, 2.0)        // both float64
**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

func Equal[T any](a, b T) bool {
    return a == b
}

Equal([]int{1}, []int{1}) // compile error
Hint Is `==` defined for slices?
Fix Use the `comparable` constraint:
func Equal[T comparable](a, b T) bool {
    return a == b
}
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

func Process[T any, U any](x T) T {
    return x
}
Hint Why is `U` declared?
Fix Remove `U`:
func Process[T any](x T) T { return x }
**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

func Half[T any](x T) T {
    return T(int(x) / 2) // compile error
}
Hint What does `int(x)` require?
Fix Constrain `T` so the conversion is valid:
func Half[T ~int | ~int64](x T) T {
    return T(int64(x) / 2)
}
**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:
Greet(&g) // T = *EnglishGreeter
**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

func Print[T interface{}](x T) {
    fmt.Println(x)
}
Hint It compiles — but does it follow modern style?
Fix
func Print[T any](x T) { fmt.Println(x) }
**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
func KeysSorted[K cmp.Ordered, V any](m map[K]V) []K { ... }
**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:
for _, x := range xs {
    x := x
    fs = append(fs, func(y T) T { return x + y })
}
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

type Weird interface { int; string }

func F[T Weird](x T) T { return x } // legal but uncallable
Hint Can a single type be both `int` and `string`?
Fix Use `|` instead of `;`:
type Number interface { int | string }
**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

func Default[T any](p *T) T {
    if p == nil {
        var z T
        return z
    }
    return *p
}
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:
// Default returns *p if p is non-nil; otherwise the zero value of T.
// If T is itself a pointer type, the returned zero value is nil.
**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:
func MaxByIdx[T any, K cmp.Ordered](xs []T, key func(T) K) (T, bool) {
    if len(xs) == 0 { var z T; return z, false }
    bestIdx := 0
    bestKey := key(xs[0])
    for i := 1; i < len(xs); i++ {
        k := key(xs[i])
        if k > bestKey { bestIdx, bestKey = i, k }
    }
    return xs[bestIdx], true
}
**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
func ToSet[T comparable](xs []T) map[T]struct{} { ... }
**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
func Stringify[T fmt.Stringer](xs []T) []string {
    out := make([]string, len(xs))
    for i, x := range xs {
        out[i] = x.String()
    }
    return out
}
**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`:
F(*p) // T = MyInt — underlying int — accepted
**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:
func ParallelMap[T, U any](
    ctx context.Context, xs []T, n int,
    f func(context.Context, T) (U, error),
) ([]U, error) {
    // bounded errgroup — see professional.md
}
**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

func IsSame[T any](a, b T) bool {
    return a == b // compile error if T is a func type
}
Hint Are functions comparable in Go?
Fix Restrict to `comparable`:
func IsSame[T comparable](a, b T) bool { return a == b }
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.

← tasks.md · optimize.md →