Day 31 — Generics intro

Updated

July 30, 2026

Day 31 — Generics intro

Stage IV · ~3h (theory-heavy)
Goal: Use type parameters and constraints confidently—write generic Map/Filter/Reduce helpers without abusing generics as a personality.

Note

Generics (Go 1.18+) remove boilerplate, not design. Stage IV starts only after Stage III so you do not hide races behind type parameters.

Why this day exists

Before generics, common patterns needed:

  • interface{} / any + type assertions
  • Code generation
  • Copy-paste with types renamed

Those still exist—but for pure algorithms over slices/maps/containers, type parameters are the right tool.


Theory 1 — Type parameters on functions

func Identity[T any](v T) T { return v }

x := Identity[int](3)
y := Identity("hi") // inference
Piece Meaning
[T any] Type parameter list; T constrained by any
Instantiation Identity[int] or inferred from args
any Alias for empty interface—unconstrained

Multiple parameters

func Pair[A, B any](a A, b B) (A, B) { return a, b }

Theory 2 — Constraints

A constraint is an interface limiting allowed type arguments.

func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}
import "cmp" // Go 1.21+: cmp.Ordered
// or golang.org/x/exp/constraints historically

Go 1.21+ prefers cmp.Ordered in many examples:

import "cmp"

func Min[T cmp.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

Inline interface constraints

type Number interface {
    ~int | ~int64 | ~float64
}

func Add[T Number](a, b T) T { return a + b }

~ means underlying type: type MyInt int satisfies ~int.


Theory 3 — Approximation (~) and unions

type Stringy interface {
    ~string
}

Union:

type Intish interface {
    ~int | ~int32 | ~int64
}

Methods + types in one interface:

type Stringer interface {
    ~string
    String() string // impossible? actually: type set + methods rules
}

Know the rules at a high level: constraints define a type set. If unsure, keep constraints simple—any, cmp.Ordered, or small unions.


Theory 4 — Inference and explicit instantiation

Map([]int{1, 2}, func(x int) int { return x + 1 })
// compiler infers T, U

Map[int, string](xs, strconv.Itoa) // explicit when needed

Inference fails when arguments don’t pin types—then instantiate explicitly.


Theory 5 — Map / Filter / Reduce (stdlib-shaped)

Go did not put full FP combinators in stdlib early; slices and maps packages grew helpers. Still, writing your own teaches constraints.

func Map[T, U any](in []T, f func(T) U) []U {
    out := make([]U, len(in))
    for i, v := range in {
        out[i] = f(v)
    }
    return out
}

func Filter[T any](in []T, keep func(T) bool) []T {
    var out []T
    for _, v := range in {
        if keep(v) {
            out = append(out, v)
        }
    }
    return out
}

func Reduce[T, U any](in []T, init U, f func(U, T) U) U {
    acc := init
    for _, v := range in {
        acc = f(acc, v)
    }
    return acc
}

Prefer stdlib when available

import "slices"

slices.Sort(xs)
slices.Compact(xs)
// etc.

Don’t reinvent a worse slices package for production—labs reinvent for learning.


Theory 6 — Generic interfaces (preview)

type Repo[T any] interface {
    Get(id string) (T, error)
    Save(T) error
}

Useful; can also over-abstract. Day 32–33 drill judgment.


Theory 7 — What generics do not solve

  • Performance magic (sometimes monomorphization helps; not why you start)
  • Removing all interfaces
  • Cross-package API churn without care
  • Hiding concurrency mistakes

Worked example — keys of a map

func Keys[M ~map[K]V, K comparable, V any](m M) []K {
    out := make([]K, 0, len(m))
    for k := range m {
        out = append(out, k)
    }
    return out
}

comparable is required for map keys.


Worked example — pointer constraint pattern

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

Simple and useful for tests.


Lab 1 — Module + Map/Filter/Reduce

Workspace: ~/lab/90daysofx/01-go/day31

mkdir -p ~/lab/90daysofx/01-go/day31
cd ~/lab/90daysofx/01-go/day31
go mod init example.com/day31

Implement Map, Filter, Reduce with tests:

go test -race ./...

(Race optional here but keep the habit.)


Lab 2 — Min/Max with cmp.Ordered

func Min[T cmp.Ordered](a, b T) T
func Max[T cmp.Ordered](a, b T) T
func Clamp[T cmp.Ordered](v, lo, hi T) T

Test with int, float64, string.


Lab 3 — ~ underlying types

type Celsius float64

Write AddTemp[T ~float64](a, b T) T and show Celsius works.


Lab 4 — Replace interface{} code

Take a small pre-generic style helper (or write one) using any + assert; rewrite with type parameters. Compare error messages and call-site clarity.


Common gotchas

Gotcha Fix
Constraint too tight Start with any, tighten
Constraint too loose Compiler can’t use operators
Fighting inference Explicit [T]
Generic everything Day 33—push back
Ignoring slices/maps Prefer stdlib in real code
Method receivers on generic funcs Methods come Day 32 types

Checkpoint

  • Writes generic functions with inference
  • Uses cmp.Ordered or union constraints
  • Explains ~
  • Ships Map/Filter/Reduce tests
  • Knows generics ≠ free performance
  • go test green

Commit

git add .
git commit -m "day31: generics intro — Map/Filter/Reduce + constraints"

Write three personal gotchas before continuing.


Tomorrow

Day 32 — Generic structs & self-referential constraints: type-parameterized data structures and Go 1.26 self-referential type parameter notes.