6 Generics
Generics add another layer of abstraction to Go by letting functions, types, and interfaces work with many concrete data types while still preserving compile-time type safety. The chapter introduces type parameters, explains why they replace many uses of duplicated type-specific code and unsafe interface{} patterns, and shows how type inference makes generic calls feel natural. Early examples center on reusable collection helpers like filtering slices, then expand into generic nodes, lists, and interfaces to show how a single pattern can serve recipes, steps, ingredients, and other domain types.
A major focus is on constraints, which control what kinds of types can be used with a generic definition. The chapter covers broad constraints such as any, more specific ones like comparable, interface-based constraints such as fmt.Stringer, and custom type sets for domains like numeric values. It also explains the tilde operator, which broadens a constraint to accept named types with the same underlying type, making generic APIs more flexible without giving up safety. From there, the chapter builds a generic linked list and a generic map, showing how to implement methods, constructors, and utility functions that preserve type relationships across multiple operations.
The chapter also explores multiple type parameters, using them to model relationships like key and value pairs in a generic map and to write functions such as merge, transform, reverse, and max-style utilities. It closes by discussing practical concerns: performance is usually close to hand-written code, but constraints, allocations, and interface conversions still matter; generics have limitations around constants, field names, operator use, and reflection; and they are best used when the same logic truly applies to multiple types. Finally, the chapter connects these ideas to the modern standard library, including packages like cmp, slices, and maps, which provide ready-made generic tools for comparison, sorting, searching, and map operations.
Generic Structure that eventually holds a string type
Type parameters in an interface are passed to implementing types and methods, ensuring that generic constraints and behaviors are enforced in the implementation
Constraint hierarchy for type parameters
A single type parameter flowing from the List interface to an instantiated LinkedList
Two type parameters flowing from the Map interface to an instantiated HashMap
Summary
- Generics allow you to write reusable, type-safe code by defining functions, types, and interfaces with type parameters.
- Type parameters can be constrained using built-in interfaces (like
anyandcomparable), standard library constraints, or custom type sets. - The tilde (
~) operator in constraints enables generic code to work with user-defined types that share the same underlying type as built-in types. - Generic interfaces and structs enable the creation of flexible data structures such as lists, sets, and maps that work with any compatible type.
- Multiple type parameters allow you to relate different types together, as in generic map implementations.
- The Go standard library provides generic packages (
constraints,slices,maps,cmp) to support common operations on collections and values. - Generics improve code reuse and maintainability, but may introduce performance considerations and new language restrictions.
FAQ
What are generics in Go, and why were they added?
Generics let you write functions, types, and interfaces that work with different data types while still being checked at compile time. They were added in Go 1.18 to reduce code duplication and improve reuse without sacrificing type safety.How does a type parameter work in a generic function?
A type parameter is a placeholder type, written in square brackets like[T any], that the compiler infers or accepts explicitly when the function is used. The same function body can then operate on any type that satisfies the constraint.What is the difference between any and comparable?
any means any type is allowed, while comparable restricts the type to values that can be compared with == and !=. Use any when no comparison is needed and comparable when equality checks are required.Why would you define a generic interface like Collection[T]?
A generic interface lets you describe behavior once for many element types. For example, Collection[T] can represent lists, sets, or maps that all operate on the same chosen type T in a type-safe way.How does the tilde operator (~) help with constraints?
The tilde allows any type whose underlying type matches the specified type to satisfy the constraint. For example, ~int matches both int and user-defined types like type MyInt int.Why use generic functions like Filter[T any] or Transform[T, U]?
These functions let you write one reusable implementation for many types instead of duplicating nearly identical code. Filter keeps elements of one type, while Transform can convert from one type to another with separate type parameters.How do generic structs like LinkedList[T] store and manipulate values?
A generic struct uses its type parameter for fields and methods, such as storing nodes of type T. Methods like Add, Get, and RemoveAt operate on that same type, keeping the structure reusable and type-safe.What do multiple type parameters such as [K comparable, V any] represent?
They let you model relationships between more than one type in a single abstraction. In a generic map, K is the key type and must be comparable, while V is the value type and can be any type.
Go in Action, Second Edition ebook for free