Generic Basics

Back

Loading concept...

Go Generics: Your Magic Box That Works With Everything! 🎁


The Story of the Magic Box

Imagine you have a magical box. This box is special—it can hold anything you put inside it: toys, candies, books, or even your pet hamster! And the best part? The box always knows exactly what’s inside and gives you back the same type of thing you put in.

Before generics, Go programmers had to make separate boxes for each type of thing:

  • A toy box (only for toys)
  • A candy box (only for candies)
  • A book box (only for books)

That’s a lot of boxes! 😅

Generics let you create one magic box that works with any type. Let’s learn how!


1. Type Parameters: Naming Your Magic Box’s Contents

What Are Type Parameters?

A type parameter is like putting a label on your magic box that says: “Whatever you put in here, I’ll remember it and give you back the same kind of thing.”

In Go, we write type parameters inside square brackets [T]. The T is just a placeholder name—like writing “STUFF” on your box.

Simple Example

// A magic box that holds ANY type
func GetFirst[T any](items []T) T {
    return items[0]
}

Let’s break this down:

  • [T any] → “T can be ANY type”
  • items []T → “Give me a list of T things”
  • T (return) → “I’ll give back one T thing”

Using Your Magic Box

// Box holds numbers
nums := []int{10, 20, 30}
first := GetFirst(nums) // Returns 10

// Same box holds words!
words := []string{"hi", "bye"}
first := GetFirst(words) // Returns "hi"

One function. Works with everything! 🎉

The Rule of Names

You can name type parameters anything:

  • T → Most common (Type)
  • K → Usually for Keys
  • V → Usually for Values
  • E → Usually for Elements
// Multiple type parameters
func Pair[K, V any](key K, val V) {
    // K is one type, V is another
}

2. Type Constraints: Setting Rules for Your Box

The Problem With “Anything Goes”

Imagine your magic box can hold anything. But what if you want to add two things together?

You can add numbers: 5 + 3 = 8 ✅ But can you add a cat and a dog? 🐱 + 🐕 = ??? ❌

Type constraints are like saying: “This box only accepts things that can be added together!”

How Constraints Work

// Without constraint - can't add!
func Add[T any](a, b T) T {
    return a + b // ERROR! Not all T can +
}

// With constraint - works!
func Add[T int | float64](a, b T) T {
    return a + b // OK! Both can +
}

The | means “OR”:

  • int | float64 = “only int OR float64 allowed”

Creating Your Own Constraint

// Define a rule: "Must be a number type"
type Number interface {
    int | int32 | int64 | float32 | float64
}

// Use your rule
func Double[T Number](n T) T {
    return n * 2
}

Now Double works with any number type, but rejects strings, structs, or other non-numbers.

Visual: How Constraints Filter Types

graph TD A["Any Type Wants In"] --> B{Passes Constraint?} B -->|int| C["✅ Allowed"] B -->|float64| C B -->|string| D["❌ Rejected"] B -->|struct| D

3. any and comparable: Built-in Magic Labels

Go gives you two pre-made constraints that are super useful!

The any Constraint

any means “I accept literally anything.”

func PrintAnything[T any](thing T) {
    fmt.Println(thing)
}

// Works with everything!
PrintAnything(42)        // number
PrintAnything("hello")   // string
PrintAnything([]int{1})  // slice

Fun fact: any is just a nickname for interface{}. Same thing, prettier name!

The comparable Constraint

comparable means “I can check if two things are equal.”

Why does this matter? Some things in Go can’t be compared:

  • Slices ❌
  • Maps ❌
  • Functions ❌

But these can be compared:

  • Numbers ✅
  • Strings ✅
  • Booleans ✅
  • Structs (if all fields are comparable) ✅

Example: Finding an Item

// Only accepts comparable types
func Contains[T comparable](
    list []T,
    target T,
) bool {
    for _, item := range list {
        if item == target { // == needs comparable!
            return true
        }
    }
    return false
}
// This works!
nums := []int{1, 2, 3}
found := Contains(nums, 2) // true

// This would NOT compile:
// slices := [][]int{{1}, {2}}
// Contains(slices, []int{1}) // ERROR!

Quick Comparison

Constraint Accepts Use When
any Everything You don’t need to compare or do math
comparable Types you can use == on You need to check equality

4. Generic Functions: Your Swiss Army Knife 🔪

What Are Generic Functions?

A generic function is a function with type parameters. It’s like a recipe that works with different ingredients!

Regular function: “Makes chocolate cake” 🎂 Generic function: “Makes ANY flavor cake you want!” 🎂🍰🧁

Anatomy of a Generic Function

func FunctionName[TypeParams Constraint](
    regularParams,
) returnType {
    // body
}

Real Examples

Example 1: Swap Any Two Things

func Swap[T any](a, b T) (T, T) {
    return b, a
}

// Usage
x, y := Swap(1, 2)       // y=1, x=2
s1, s2 := Swap("a", "b") // s2="a", s1="b"

Example 2: Get Keys From Any Map

func Keys[K comparable, V any](
    m map[K]V,
) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

// Usage
ages := map[string]int{"Alice": 30}
names := Keys(ages) // ["Alice"]

Example 3: Find Maximum Value

type Ordered interface {
    int | float64 | string
}

func Max[T Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// Usage
Max(5, 10)       // 10
Max("a", "z")    // "z"
Max(3.14, 2.71)  // 3.14

The Magic Flow

graph TD A["Write Generic Function"] --> B["Add Type Parameter"] B --> C["Set Constraint"] C --> D["Use in Function Body"] D --> E["Call with Any Valid Type"] E --> F["Go Figures Out the Type!"]

Type Inference: Go’s Smart Brain 🧠

When you call a generic function, Go is smart enough to figure out the type automatically!

// You CAN write this:
result := Max[int](5, 10)

// But you don't have to! Go figures it out:
result := Max(5, 10) // Go knows it's int

This is called type inference. Go looks at what you pass in and says, “Ah, those are ints, so T must be int!”


Summary: Your Generics Toolkit

Concept What It Does Syntax
Type Parameter A placeholder for any type [T]
Type Constraint Rules for what types are allowed [T SomeRule]
any Accept any type [T any]
comparable Accept types that support == [T comparable]
Generic Function One function that works with many types func Name[T C](...)

The Confidence Boost 🚀

You now understand the four pillars of Go generics:

  1. Type Parameters → Name your flexible types with [T]
  2. Type Constraints → Set rules with interfaces or |
  3. any & comparable → Use Go’s built-in helpers
  4. Generic Functions → Write once, use everywhere

Before generics, Go developers wrote the same code over and over for different types. Now, you write it once and it works with everything that fits your rules.

That’s not just coding—that’s magic! ✨


Go forth and make your code generic! One function to rule them all. 🏆

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.