For years, Go developers relied on interface{} (now any) and type assertions to handle different data types. While it worked, it often felt clunky and sacrificed the very type safety that makes Go so powerful. If you’ve ever found yourself writing the exact same function three times—once for int, once for int64, and once for float64—then this go generics tutorial for beginners is for you.
In my experience building automation tools at ajmani.dev, generics have drastically reduced the amount of boilerplate code I maintain. Instead of duplicating logic, I can now define the behavior once and let the compiler handle the specific types.
Core Concepts: What are Generics?
At its heart, generics allow you to write functions and data structures where the type of the data they operate on is specified as a parameter. Think of it as a “placeholder” for a type that gets filled in when the function is actually called.
Type Parameters
A type parameter is the placeholder we use. In Go, these are placed inside square brackets [] immediately after the function name. By convention, we usually use T (for Type), but you can name them whatever you like.
Type Constraints
You can’t just let T be anything if you plan on using operators like + or >. A type constraint tells the compiler, “T can be any type, as long as it supports these specific operations.” For example, the comparable constraint is built-in and allows you to use the == and != operators.
Getting Started: Your First Generic Function
Let’s look at a classic problem: finding the sum of a slice. Without generics, you’d need a separate function for every numeric type. Here is how we solve it using generics.
package main
import "fmt"
// Num is a custom constraint that allows any numeric type
type Num interface {
int | int64 | float64
}
// Sum adds up elements of a slice of any numeric type
func Sum[T Num](s []T) T {
var total T
for _, v := range s {
total += v
}
return total
}
func main() {
ints := []int{1, 2, 3}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(ints)) // Output: 6
fmt.Println(Sum(floats)) // Output: 6.6
}
As shown in the logic above, the Sum function doesn’t care if it’s handling integers or floats, as long as the type satisfies the Num interface. This is a cornerstone of modern go language features 2026 that every developer should master.
First Project: A Generic Cache Wrapper
To put this into practice, I recommend building a simple generic cache. This is a common pattern in production environments to avoid repetitive map logic. Here is a simplified version of how I implement these in my own golang project structure best practices.
package main
import "fmt"
type Cache[K comparable, V any] struct {
store map[K]V
}
func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{
store: make(map[K]V),
}
}
func (c *Cache[K, V]) Set(key K, value V) {
c.store[key] = value
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
val, ok := c.store[key]
return val, ok
}
func main() {
// A cache where keys are strings and values are ints
ageCache := NewCache[string, int]()
ageCache.Set("Alice", 30)
// A cache where keys are ints and values are strings
idCache := NewCache[int, string]()
idCache.Set(101, "User_A")
fmt.Println(ageCache.Get("Alice"))
fmt.Println(idCache.Get(101))
}
Common Mistakes When Using Generics
When I first started using generics, I fell into the trap of over-engineering. Here are the most common pitfalls to avoid:
- Using Generics Everywhere: Don’t use generics if a simple interface will do. Generics are for when you need to return the same type that was passed in or when you’re dealing with collections.
- Ignoring the ‘any’ constraint: If your function doesn’t perform any operations on the type (like just moving it from one slice to another), use
any. Don’t create custom constraints for no reason. - Complexity Overload: If your type constraints look like a novel, your function is probably doing too much. Break it down.
Learning Path for Go Developers
If you’re looking to move beyond this basic tutorial, I suggest this progression:
- Master Constraints: Learn about the
cmp.Orderedconstraint in the standard library for sorting. - Generic Data Structures: Try implementing a Linked List or a Binary Tree using generics.
- Refactor Existing Code: Find a place in your current project where you have duplicated functions for different types and refactor them.
Recommended Tools
To make your experience smoother, I recommend:
- VS Code with Go Extension: The type inference in the official extension is excellent and will show you exactly what
Tis during hover. - Go Playground: Perfect for testing small generic snippets without setting up a full module.
- golangci-lint: Use this to catch common misuse of generics and ensure your code stays idiomatic.
Ready to optimize your Go workflow? Start by auditing your utility packages for duplication today!