Generics
Generics
Introduction
In this lesson, you'll learn about generics in Go. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with generics.
Go has its own approach to generics, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
// Go 1.18+ generics
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, pred func(T) bool) []T {
var result []T
for _, v := range slice {
if pred(v) { result = append(result, v) }
}
return result
}
// Constrained generics with type sets
type Number interface {
~int | ~float64
}
func Max[T Number](a, b T) T {
if a >= b { return a }
return b
}Comparing to TypeScript
Here's how you might have written similar code in TypeScript:
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
function filter<T>(arr: T[], pred: (item: T) => boolean): T[] {
return arr.filter(pred);
}
// Constrained generics
function max<T extends { valueOf(): number }>(a: T, b: T): T {
return a.valueOf() >= b.valueOf() ? a : b;
}You may be used to different syntax or behavior.
Go 1.18+ added generics with [T any] syntax — similar to TypeScript's <T>
You may be used to different syntax or behavior.
Go constraints use interface{} type sets with | — like TypeScript union constraints
You may be used to different syntax or behavior.
The ~ prefix means 'any type whose underlying type is T' (e.g., ~int includes custom int types)
You may be used to different syntax or behavior.
Go generics are more limited than TypeScript — no conditional types or mapped types
Step-by-Step Breakdown
1. [T any] vs <T>
Go generics use [T any] where TypeScript uses <T>. any is the unconstrained type parameter — like TypeScript's unknown.
function identity<T>(v: T): T { return v; }func Identity[T any](v T) T { return v }2. Type Sets for Constraints
Go constraints are interfaces that list acceptable types. The | operator creates a union. ~ means 'underlying type'. This replaces TypeScript's T extends union.
function add<T extends number | string>(a: T, b: T): T {}type Addable interface {
~int | ~float64 | ~string
}
func Add[T Addable](a, b T) T { return a + b }3. golang.org/x/exp/slices
The standard library was slow to adopt generics. The golang.org/x/exp package provides generic slice utilities (slices.Map, slices.Filter) as the community standard.
import { pipe } from "fp-ts/function"; // TS functional libs// Go standard (1.21+):
import "slices"
sorted := slices.Sorted(slices.Values(mySlice))
idx, found := slices.BinarySearch(mySlice, target)4. No Specialization
Go generics cannot be specialized for specific types. You cannot have different behavior for Map[string] vs Map[int]. TypeScript conditional types fill this gap but Go takes a simpler approach.
type StringOrNumber<T> = T extends string ? string : number;// Not possible in Go — generics are uniform
// Use type switches for runtime type-specific behavior:
func describe(v any) string {
switch x := v.(type) {
case int: return fmt.Sprintf("int: %d", x)
case string: return fmt.Sprintf("str: %s", x)
default: return "unknown"
}
}Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- Go 1.18+ added generics with [T any] syntax — similar to TypeScript's <T>
- Go constraints use interface{} type sets with | — like TypeScript union constraints
- The ~ prefix means 'any type whose underlying type is T' (e.g., ~int includes custom int types)
Key Takeaways
- Go 1.18+ generics use [T any] syntax — similar to <T> in TypeScript
- Constraints use type set interfaces with | for unions and ~ for underlying types
- Go generics are intentionally simpler — no conditional or mapped types
- Standard library generics (slices, maps packages) available from Go 1.21+