Introduction to Generics
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 67 of 107.
Before Go 1.18, writing reusable code for different types meant either duplicating functions or using the empty interface any with type assertions. Generics solve this problem by letting you write functions and types that work with multiple types while keeping full type safety.
Consider finding the minimum value in a slice. Without generics, you'd need separate functions for each type:
func MinInt(values []int) int {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}
func MinFloat64(values []float64) float64 {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}With generics, you write the logic once using a type parameter in square brackets:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}The [T int | float64] declares a type parameter T that can be either int or float64. Now you can call Min([]int{3, 1, 2}) or Min([]float64{3.5, 1.2}) with the same function. The compiler checks types at compile time, so you get safety without runtime overhead.
In the upcoming lessons, we'll explore type parameters, constraints, and generic structs in detail.
Challenge
EasyLet's build a score analyzer that works with both integer and floating-point scores using generics! You'll create a single generic function that can find the maximum value in a slice, eliminating the need for duplicate code for different numeric types.
You'll organize your code across two files:
analyzer.go: Define your generic analysis function.Create a generic function
Max[T int | float64](values []T) Tthat finds and returns the maximum value in a slice. The function should work with bothintandfloat64types through the type parameterT.Also create a generic function
Sum[T int | float64](values []T) Tthat calculates and returns the sum of all values in a slice.main.go: Read input and demonstrate your generic functions with different types.Read a type indicator (
intorfloat), then read a count followed by that many values. Parse the values according to the type, call bothMaxandSumwith the appropriate type, and print the results.For integer input, print:
Max (int): [value] Sum (int): [value]For float input, print with one decimal place:
Max (float): [value] Sum (float): [value]
The following inputs will be provided:
- Line 1: Type indicator (
intorfloat) - Line 2: Count of values (integer)
- Following lines: One value per line
For example, given:
int
4
15
8
23
11Your output should be:
Max (int): 23
Sum (int): 57And given:
float
3
4.5
9.2
6.8Your output should be:
Max (float): 9.2
Sum (float): 20.5Notice how the same generic functions handle both integer and floating-point data—the type parameter [T int | float64] allows the compiler to generate type-safe code for each usage while you only write the logic once.
Cheat sheet
Generics allow you to write reusable code that works with multiple types while maintaining type safety. Before Go 1.18, you needed separate functions for each type or used the empty interface with type assertions.
Type parameters are declared in square brackets and specify which types a generic function can accept:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}The [T int | float64] syntax declares a type parameter T that can be either int or float64. The pipe | operator lists the allowed types.
You can call generic functions with different types:
Min([]int{3, 1, 2}) // Works with int
Min([]float64{3.5, 1.2}) // Works with float64The compiler performs type checking at compile time, providing safety without runtime overhead.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read type indicator
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// Read count
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// Read integer values
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: Call Max and Sum with integer slice
// TODO: Print results in format "Max (int): [value]" and "Sum (int): [value]"
} else if typeStr == "float" {
// Read float values
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: Call Max and Sum with float64 slice
// TODO: Print results with one decimal place in format "Max (float): [value]" and "Sum (float): [value]"
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool