Crash Course on Go Generics

This post was originally written for my mailing list. If you want to get awesome Go tutorials like this delivered to your inbox, sign up for my mailing list!

Generics are a way of writing type-safe code in a statically typed language that can work with more than one type.

Prior to having generics, we would need to write code separately for each type we wanted to support. If we wanted to write code to sum up a slice of ints, the code would look like the code below.

func Sum(numbers []int) int {
	var sum int
	for _, number := range numbers {
		sum += number
  }
	return sum
}

If we want to sum a slice of int64 values, or float64 values, this code wouldn’t work. We would need to write another function for each type (int64, float64, etc) or use code code generation. An even worse option would be to use reflection, which might work, but would result in code that is much harder to maintain, loses out on some compile-time type safety, and is likely less performant.

With generics, we can write this code once in a way that it works with all three of these types. The compiler will still ensure we are using static types safely, and it isn’t much harder to understand or maintain.


func Sum[T int | int64 | float64](numbers []T) T {
	var sum T
	for _, number := range numbers {
		sum += number
	}
	return sum
}

What our code is now saying is that we need a slice with a type of int, int64, or float64. We then represent that type in our code with the letter T. While single characters like T are common in generic code, it could be any identifier, even something silly like SuperAwesomeNumber

func Sum[SuperAwesomeNumber int | int64 | float64](numbers []SuperAwesomeNumber) SuperAwesomeNumber {
	var sum SuperAwesomeNumber
	for _, number := range numbers {
		sum += number
	}
	return sum
}

We can also take the types we want to support and declare them as a type. This helps make writing generics a little easier to maintain and read.


type Number interface {
	int | int64 | float64
}

func Sum[T Number](numbers []T) T {
	var sum T
	for _, number := range numbers {
		sum += number
	}
	return sum
}

One key point to note with this code is that we still end up using a static type at compile time. Our function can’t accept an integer slice ([]int) as the argument and return a float64. The compiler will throw an error if we try to do that, because our function states that if we receiver an integer slice, we will return an int.

This is where two of the most common generics misconceptions occur. Many people incorrectly assume that generics mean that we can have a slice with mixed data types, or that we can have a function that returns one of two types determined at runtime.

// Misconception #1: We cannot define a slice with mixed data even though
// the Number type makes it look possible. The following code will NOT work.
type Number interface {
	int | int64 | float64
}
numbers := []Number{1, 1.10}
//              int ^    ^ float64
// Misconception #2: We cannot return different types depending on what happens
// at runtime. T will either be the int or error type at compile time, not both.
func DoSomething[T int | error]() T {
  if err != nil {
    return err
  }
  return 10
}

For the second misconception, we still need to use the common Go pattern of returning both an int and an error. Generics don’t really help us out here.

func DoSomething() (int, error) {
  if err != nil {
    return 0, err
  }
  return 10, nil
}

Some other languages like Rust have a Maybe or Option type, and this functionality could be created somewhat in Go, but in my limited experience it was always better to return the error at the end instead, as this is what every Gopher is used to at this point.

In addition to writing functions that work with various data types, generics are also incredibly useful for creating container types. For instance, if we wanted to create a list type that always kept its values in sorted order, we could.


type Number interface {
	int | int64 | float64
}

// Warning: This type is inefficient and is only meant to illustrate the
// point that this is possible with generics.
type SortedList[T Number] struct {
	values []T
}

func (list *SortedList[T]) Values() []T {
	// copy the values when returning them
	return append([]T{}, list.values...)
}

func (list *SortedList[T]) AddValue(newValue T) {
	for i, v := range list.values {
		if v > newValue {
			list.values = append(list.values[:i+1], list.values[i:]...)
			list.values[i] = newValue
			return
		}
	}
	list.values = append(list.values, newValue)
}

There are many of other use cases for generics out there, and hopefully this helps shed some light on what some of them might be. You may not need to implement many of these on your own, but I suspect as you continue developing with Go you will find yourself using types that utilize generics behind the scenes, so it can be helpful to understand how the code works.

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.

You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.

Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.

Related articles

Spread the word

Did you find this page helpful? Let others know about it!

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Progress Updates Tags About Me Go Courses

©2024 Jonathan Calhoun. All rights reserved.