Golang Generics Explained (Functions, Structs, JSON & Real Examples)

Golang Generics Explained (Functions, Structs, JSON & Real Examples)

Golang Generics

Generic Syntax Explained (T any, Constraints)

go
func Print[T any](v T) {}
func Sum[T int | float64](a, b T) T {}
  • T → type parameter
  • any → allows any type
  • Constraints → restrict allowed types

Common Generic Patterns (Functions, Structs, Interfaces)

go
// Generic function
func Max[T int | float64](a, b T) T { return a }

// Generic struct
type Box[T any] struct { value T }

// Generic map helper
func Keys[K comparable, V any](m map[K]V) []K {}

When to Use Generics (Quick Rules)

  • Avoid duplicate functions for different types
  • Use for reusable utilities (Max, Filter, Map)
  • Use constraints for type safety

When NOT to Use Generics

  • Simple one-type logic
  • When interface is cleaner
  • Over-abstraction cases

What are Generics in Golang

Generics in Golang allow you to write reusable and type-safe code by defining functions and types that can work with multiple data types without duplicating logic.

Before Go 1.18, developers had to either:

  • write multiple functions for different types, or
  • use interface{} (which loses type safety)

Generics solve this by introducing type parameters, making code reusable while maintaining compile-time checks.

Why Generics Were Introduced in Go

Generics were introduced to reduce code duplication and improve type safety.

Problem without generics:

go
func SumInt(nums []int) int { ... }
func SumFloat(nums []float64) float64 { ... }

You must write separate functions for each type.

With generics:

go
func Sum[T int | float64](nums []T) T {
    var sum T
    for _, n := range nums {
        sum += n
    }
    return sum
}

Explanation:

  • T is a type parameter
  • T int | float64 restricts allowed types
  • One function replaces multiple implementations

Benefits of Generics:

  • Reduces duplicate code
  • Improves readability
  • Maintains type safety
  • Better performance than interface{} in many cases

Generics vs Interface (When to Use What)

Generics and interfaces solve similar problems but are used differently.

FeatureGenericsInterface
Type SafetyStrong (compile-time)Weak (runtime)
PerformanceFasterSlower
FlexibilityLimited by constraintsMore flexible
Use CaseReusable logicBehavior abstraction

Use Generics when:

  • Same logic applies to multiple data types
  • You want compile-time safety
  • You are working with collections (slice, map, etc.)

Use Interface when:

  • You need polymorphism (different behaviors)
  • Types share common methods
  • You are designing APIs

Example: Interface vs Generic

go
// Interface example
type Shape interface {
    Area() float64
}
go
// Generic example
func Print[T any](v T) {
    fmt.Println(v)
}

Generic Functions in Golang

Generic functions are the most common use of generics in Go. They allow you to write one function that works with multiple data types.

Create Generic Function with Example

go
package main

import "fmt"

func PrintSlice[T any](items []T) {
    for _, v := range items {
        fmt.Println(v)
    }
}

func main() {
    nums := []int{1, 2, 3}
    words := []string{"Go", "Generics"}

    PrintSlice(nums)
    PrintSlice(words)
}

Explanation:

  • PrintSlice[T any] accepts a slice of any type
  • T represents the element type
  • Same function works for int, string, etc.

Generic Function with Multiple Types (int, float, string)

You can restrict generics to specific types using constraints.

go
package main

import "fmt"

func Max[T int | float64 | string](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Max(10, 20))
    fmt.Println(Max(5.5, 3.2))
    fmt.Println(Max("apple", "banana"))
}

Explanation:

  • T int | float64 | string allows only these types
  • Function works across multiple data types
  • Ensures invalid types are rejected at compile time
IMPORTANT
  • Operations like > must be supported by all types in constraint
  • Otherwise, code will fail to compile

Generic Types (Structs and Custom Types)

Generics in Go are not limited to functions — you can also define generic types, such as structs and custom constraints, to build reusable data structures.

Generic Struct Example in Go

A generic struct allows you to define a structure that can hold values of any type.

go
package main

import "fmt"

type Box[T any] struct {
    value T
}

func main() {
    intBox := Box[int]{value: 10}
    strBox := Box[string]{value: "hello"}

    fmt.Println(intBox.value)
    fmt.Println(strBox.value)
}

Explanation:

  • Box[T any] defines a generic struct
  • T represents the type stored inside the struct
  • You can create instances with different types (int, string, etc.)

Generic Type Constraints using Interface

Instead of repeating multiple types in a function, you can define a reusable constraint using an interface.

go
package main

import "fmt"

type Number interface {
    int | float64
}

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

func main() {
    fmt.Println(Sum([]int{1, 2, 3}))
    fmt.Println(Sum([]float64{1.5, 2.5}))
}

Explanation:

  • Number is a custom type constraint
  • It defines allowed types (int, float64)
  • Makes function signature cleaner and reusable

Why use constraints:

  • Improves readability
  • Avoids long type unions
  • Enables reusable type rules

Advanced Generics (Real-World Use Cases)

Generic JSON Parsing in Golang

Generics simplify JSON parsing by removing repetitive struct conversion logic.

go
package main

import (
    "encoding/json"
    "fmt"
)

func ParseJSON[T any](data []byte) (T, error) {
    var result T
    err := json.Unmarshal(data, &result)
    return result, err
}

type User struct {
    Name string
    Age  int
}

func main() {
    jsonData := []byte(`{"Name":"John","Age":30}`)

    user, err := ParseJSON[User](jsonData)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    fmt.Println(user.Name, user.Age)
}

Explanation:

  • ParseJSON[T any] works with any struct
  • Removes need for repeated parsing functions
  • Ensures type-safe JSON handling

Generic Utility Functions (Max, Filter, Map)

Generics are commonly used to build reusable utility functions.

Max function:

go
func Max[T int | float64](a, b T) T {
    if a > b {
        return a
    }
    return b
}

Filter function:

go
func Filter[T any](items []T, fn func(T) bool) []T {
    var result []T
    for _, item := range items {
        if fn(item) {
            result = append(result, item)
        }
    }
    return result
}

Map function:

go
func Map[T any, R any](items []T, fn func(T) R) []R {
    var result []R
    for _, item := range items {
        result = append(result, fn(item))
    }
    return result
}

Explanation:

  • Max works for numeric comparisons
  • Filter processes elements based on condition
  • Map transforms one type into another

Generic Methods in Go

Go does not allow methods to define their own type parameters independently, but you can define methods on generic types.

go
package main

import "fmt"

type Container[T any] struct {
    value T
}

func (c Container[T]) Get() T {
    return c.value
}

func main() {
    c := Container[int]{value: 100}
    fmt.Println(c.Get())
}

Explanation:

  • Container[T] is a generic type
  • Method Get() works with that generic type
  • Type parameter is defined at struct level, not method level

Important Limitation:

❌ This is NOT allowed:

go
func (c Container) Get[T any]() T {} // invalid

Real-world use case:

  • Generic data wrappers
  • Service layers
  • Repository patterns

Frequently Asked Questions

1. What are generics in Golang?

Generics in Golang allow developers to write reusable and type-safe functions and types using type parameters, reducing code duplication.

2. When should I use generics in Go?

Use generics when the same logic needs to work with multiple data types, such as slices, maps, or utility functions like Max and Filter.

3. What is T any in Golang generics?

T any defines a type parameter where T can be any type. It is equivalent to interface{} but with compile-time type safety.

4. What is the difference between generics and interface in Go?

Generics provide compile-time type safety and better performance, while interfaces provide runtime flexibility and polymorphism.

5. Can generics be used with structs and JSON in Go?

Yes, generics can be used with structs and JSON parsing to create reusable and type-safe data handling functions.

Summary

  • Golang generics allow you to write reusable and type-safe code using type parameters like T any.
  • They eliminate the need to write duplicate functions for different data types such as int, float64, or string.
  • Generic functions are the most common use case and help simplify operations like filtering, mapping, and comparisons.
  • Generic structs enable flexible data structures such as containers, wrappers, and reusable models.
  • Type constraints using interfaces allow you to restrict allowed types and improve code safety.
  • Generics are best suited for working with collections (slices, maps) and utility functions.
  • Compared to interfaces, generics provide better performance and compile-time type safety.
  • Advanced use cases include JSON parsing, reusable utilities, and data processing pipelines.
  • Generics should be used when they simplify code, but overuse can reduce readability.

Official Documentation

Antony Shikubu

Antony Shikubu

Systems Integration Engineer

Highly skilled software developer with expertise in Python, Golang, and AWS cloud services.