
Israel Alagbe
A Full-Stack Engineer with 8+ years of experience building scalable, secure and high-performance systems. I work across modern frontends, backend APIs and distributed event-driven microservice architectures, and I’m comfortable taking products from concept to production. I focus on reliability, maintainability and solid user experience, and I work well in cross-functional, remote teams.
Article by Gigson Expert
Go Generics, introduced in Go 1.18, represent a fundamental shift in how developers write reusable, type-safe code. Before generics, achieving reusability often meant using the interface{} type, which sacrificed type safety and required expensive runtime reflection.
Generics allow you to write functions and data structures that can operate on values of any type, while still preserving type checking at compile time. This leads to cleaner, more efficient, and more idiomatic Go code.
1. The Problem Generics Solves (The Pre-Generics Way)
Before generics, if you wanted a function to sum the elements of an array, you had to write a separate function for each numeric type (int, int32, float64, etc.).
The Inefficient Approach: Overloading Functions

This is redundant and non-idiomatic. You are writing the same logic repeatedly just for different types.
2. Introducing Type Parameters with Real-World Examples
Generics allow us to write functions that operate on a collection of items, regardless of what those items are, as long as they satisfy a certain constraint (rule).
2.1 Example: The PrintDetails Generic Function
Let's say we want a function that can take any list of items (Cars, Dogs, or even just integers) and print them. Since we don't perform any specific operations on the item type itself, we can use the most permissive constraint: any.

OUTPUT

3. Defining Custom Type Constraints for Specific Behavior
When your generic function needs to perform a specific operation (like addition, comparison, or checking a characteristic), you must use a custom interface constraint to tell the compiler which types are allowed.
3.1 Example: The CanTransport Constraint
Imagine you want a generic function, StartTrip, that can only be called on types that are capable of transporting something. This means we need to define an interface that only allows Car, Bus, and Train types.
First, define the types and the custom constraint interface:

Now, define the generic function StartTrip and call it from main:

OUTPUT:

Calling StartTrip with a type of Car, Bus or Train with result in a compilation error because string is not in CanTransport type set.

3.1 The comparable Constraint (Finding Items)
Imagine you want a generic function, StartTrip, that can only be called on types that are capable of transporting something. This means we need to define an interface that only allows Car, Bus, and Train types.
The built-in comparable constraint is essential when you need to use the equality operators (== or !=) inside your generic function.
Example: The FindItem Generic Function
We can write a function to find an item in a slice. The item must be comparable so we can check if it matches the target.

Output:

Summary Table: Generic Concepts

Conclusion
Generics is a game-changer for the Go language. They provide the necessary tools to write highly reusable, type-safe code without sacrificing the simplicity or performance Go is known for. By mastering the use of type parameters and type constraints (via interfaces), you can eliminate boiler-plate code, significantly improve the clarity of utility libraries and write more efficient and expressive Go programs that are easier to maintain and scale. Adopt generics strategically, focusing on library code and enjoy cleaner, more idiomatic Go.
Frequently Asked Questions (FAQs)
1. Why use generics when I can just use interface{}?
While interface{} (or any) allows a function to accept any type, it loses all type safety. This means the compiler cannot check for errors, forcing you to use expensive runtime reflection and type assertions. Generics, conversely, enforce type checks at compile time, providing the same flexibility but with complete type safety and significantly better performance.
2. Do generics slow down my Go program?
Generally, no. The Go compiler uses a technique called monomorphization (or "stenciling"). For every concrete type used with a generic function (e.g., calling Sum[int] and Sum[float64]), the compiler generates a specialized version of the machine code. This means the final compiled binary runs at native speed, identical to code you would have written manually.
3. Can I use generics in methods attached to a struct?
No. You can define type parameters on a struct declaration (e.g., type Stack[T any] struct{...}), but you cannot define new type parameters on a method attached to that struct. The type parameters for the method must be the same as those defined on the struct itself.
4. When should I create a custom type constraint interface?
You need a custom constraint interface when you want to use operators (+, -, *) or methods that are not guaranteed by the built-in any or comparable interfaces. For example, if your generic function needs to call a Run() method, the constraint interface must explicitly list the allowed types that implement that method.
5. What is the difference between any and comparable?
any: The most liberal constraint, allowing any type. This is equivalent to the oldinterface{}. You cannot use equality operators (==) or any other operation on this type unless it's explicitly part of an interface.comparable: A built-in constraint that allows any type that supports the equality operators (==and!=). This is essential for keys in generic maps and any function that requires item comparison




.webp)