Why Go Generics? An Introduction to Parametric Polymorphism

Simplifying Code: How Go Generics Solve the Problem of Duplicated Logic

image.png|300

Before We Begin

Over the past while, work has kept me quite busy, leaving little time for writing. In my spare moments, I found myself drawn into the stock market, which, to some extent, ended up wasting some time.

In the age of AI, using AI as a learning tool feels like the right approach. Going forward, I’ll try to set aside more time to learn new things and share them in a timely manner, hoping to maintain a more consistent publishing rhythm.

Being influenced by AI is inevitable in this era. I choose to embrace and adapt rather than resist it. In today’s fast-moving landscape, preserving personal storytelling ability and continuing to grow is a significant challenge—one that may well be a challenge for humanity itself. Perhaps this is how we prevent ourselves from gradually regressing. For now, while AI can already do many things remarkably well, I still believe it remains human-centered and ultimately serves people.

Whether AI will truly replace humans may not be far off—who knows 🤷‍♂️? For now, it’s better to stay focused on what’s right in front of us.

Preface

In my daily work, I rarely make use of Go’s generics. I feel it’s time to start learning and mastering them step by step.

Generic programming, also known as parametric polymorphism, is a method of reducing function duplication by writing “common functions” that support multiple type parameters or arguments. In the context of the Go programming language, generics were a highly anticipated feature that was finally added to the core in the Go v1.18 release. The overall purpose of adding generics is to avoid boilerplate code and the duplication of logic, allowing developers to write libraries and functions that operate on arbitrary types or values.

1. The Problem Case: Programming Before Generics

Before the introduction of generics, every function in a statically typed language like Go had to apply to a particular type. If a developer wanted to implement the same logic for different data types—such as int, int64, or string—they were forced to implement separate, nearly identical functions for each.

In a large codebase, this often leads to thousands of lines of duplicated code for simple utilities like finding an element in an array or reversing a slice. This approach directly contradicts the DRY (Don’t Repeat Yourself) principle.

Code Example: Duplication Without Generics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Reversing a slice of integers
func ReverseInts(s []int) {
first, last := 0, len(s)-1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}

// Reversing a slice of strings requires an entirely new function
func ReverseStrings(s []string) {
first, last := 0, len(s)-1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}

In the example above, the logic is identical, yet the type signature forces the duplication. While developers previously used interfaces and type assertions to handle multiple types, this method was often awkward, required manual type switches, and sacrificed the benefits of compile-time type safety.

2. The Motivation for Generics

The primary motivation for generics is the ability to factor out the element type from algorithms and data structures. This allows developers to:

  • Write once, use everywhere: Reusable utility functions (like Map, Filter, and Reduce) can be written once and bundled as a package for any value type.
  • Type-Safe Containers: Generics allow for the creation of containers (like sets or trees) that are compile-time type-safe, meaning the compiler can check the values they hold without needing empty interfaces.
  • Reduced Boilerplate: By eliminating the need for separate implementations for every unique struct or primitive, the codebase becomes smaller and easier to maintain.

3. How Generics Solve the Problem

Generics introduce type parameters that act as placeholders for types that will be specified when the function is called. These parameters are often bounded by constraints, which are interface types that define what operations the generic type must support.

Code Example: Reusable Logic With Generics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// A single generic function that works for any slice type
func Reverse[T any](s []T) {
first, last := 0, len(s)-1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}

func main() {
ints := []int{1, 2, 3}
strings := []string{"a", "b", "c"}

// The same function handles both types safely
Reverse(ints)
Reverse(strings)
}

In this version, T is a type parameter, and the any constraint allows it to represent any type. The compiler uses type inference so that the caller often doesn’t even need to explicitly mention the type argument.

4. When to Use (and Not Use) Generics

While powerful, generics add a layer of complexity to the language, and the Go team suggests they should be used judiciously.

  • Use generics when: You are implementing a solution that requires the same behavior for different data types, such as functions operating on container types (maps, slices, channels) or general-purpose data structures.
  • Do not use generics when: You are simply calling a method on a type argument (use an interface type instead) or when you need to perform different operations for each specific type (use the reflection API).

Conclusion

Generics make statically typed languages like Go safer, more efficient, and more powerful by allowing for the implementation of optimized, debugged libraries that apply to a wide variety of types. They shift the complexity from the user of a library to the writer, ensuring that everyday code remains simple and readable while gaining significant flexibility.

Analogy for Understanding: Think of generics like a high-quality kitchen mold. Without generics, you would need a separate, unique mold for every material—one specifically for chocolate, one for gelatin, and another for ice—even if you wanted them all to be the same star shape. With generics, you have a single universal star mold that you can pour any “material” (type) into; as long as the material can be poured and frozen (the constraints), the mold will produce a perfect star every time without you needing to reinvent the tool.


Quoted Articles and Sources

Recent Articles:

Random Article:


More Series Articles about You Should Know In Golang:

https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a

And I’m Wesley, delighted to share knowledge from the world of programming. 

Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. It would be a great help to me.

Give me some free applauds, highlights, or replies, and I’ll pay attention to those reactions, which will determine whether I continue to post this type of article.

See you in the next article. 👋

中文文章: https://programmerscareer.com/zh-cn/go-generics-01/
Author: Medium,LinkedIn,Twitter
Note: Originally written at https://programmerscareer.com/go-generics-01/ at 2026-01-01 10:37.
Copyright: BY-NC-ND 3.0

The Syntax of Generics in Go: Declaring Functions and Types Rob Pike's Reaction to GenAI: The Collision of Simplicity and Complexity

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×