Building a Generic Accumulator in Go: A Deep Dive into Go Generics

Learn how to build a generic accumulator in Go using type parameters and constraints to track sums, counts, averages, maximums, and minimums.
Written by
Nathan Crocker
Published on
August 10, 2026

Go introduced generics in version 1.18, opening up exciting possibilities for creating more flexible and reusable code. In this article, we’ll explore how to build a generic Accumulator that can work with both integers and floating-point numbers, giving you a reusable tool for tracking sums, counts, maximums, minimums, and averages.

This article will guide you through building a generic accumulator using Go’s type parameters, constraint interfaces, and some core features of the Go language.

Why Build a Generic Accumulator?

An accumulator is a simple data structure used to collect and analyze numerical data, like sums, counts, averages, and more. Without generics, we would need to write separate code for each numeric type (e.g., int, float64, etc.). Generics allow us to write this logic once and apply it to multiple types, making the code cleaner and easier to maintain.

Let’s walk through the design and implementation of our generic accumulator.

Defining a Numeric Type Constraint

The first step in creating a generic accumulator is defining the types that will be allowed. In our case, we want the accumulator to work with both integers and floating-point numbers.

Go provides the constraints package, which contains predefined interfaces for numeric types. Here’s how we define a constraint called Numeric:

import “golang.org/x/exp/constraints”
type Numeric interface {
 constraints.Integer | constraints.Float
}

This constraint tells the compiler that Numeric can be any type that satisfies either the Integer or Float constraint. This is a key feature of Go’s generics: restricting type parameters to types that meet specific conditions.

type Accumulator[T Numeric] struct {
 Sum   T
 Count float64
 Max   T
 Min   T
}

The type parameter T is the generic type that must satisfy the Numeric constraint. This allows us to use any numeric type (e.g., int, float64) when creating an accumulator.

Initializing a New Accumulator

Next, we need a function to create and initialize a new accumulator. This function will ensure that the initial values for Sum, Count, Max, and Min are properly set:

func NewAccumulator[T Numeric]() *Accumulator[T] {
 var zero T
 return &Accumulator[T]{
  Sum:   zero,
  Count: 0,
  Max:   zero,
  Min:   zero,
 }
}

In Go, generic functions use type parameters, which we specify with T. In this case, NewAccumulator returns a pointer to a new Accumulator[T] where all the fields are initialized to zero. We use var zero T to initialize a zero value for the generic type.

Adding Values to the Accumulator

The Add method allows us to add a value to the accumulator and update the sum, count, max, and min:

func (a *Accumulator[T]) Add(value T) {
 a.AddC(value, 1)
}

This method calls a helper method AddC (short for “Add with Count”) that also allows us to specify how many times to add the value:

func (a *Accumulator[T]) AddC(value T, count float64) {
 if a.Count == 0 {
  a.Max = value
  a.Min = value
 } else {
  if value > a.Max {
   a.Max = value
  }
  if value < a.Min {
   a.Min = value
  }
 }
 a.Sum += value
 a.Count += count
}

In AddC, we check whether the count is zero, which would indicate that this is the first value being added. If it is, we initialize the max and min to the value being added. Otherwise, we compare the new value to the existing max and min to determine whether they need to be updated.

Calculating the Average

To make our accumulator more useful, we add a method to calculate the average of the values:

func (a *Accumulator[T]) Avg() T {
 if a.Count == 0 {
  var zero T
  return zero
 }
 return a.Sum / T(a.Count)
}

This method checks if any values have been added. If not, it returns zero. Otherwise, it calculates the average by dividing the sum by the count. Since Count is a float64, we need to convert it to the generic type T.

Resetting the Accumulator

Sometimes, it’s useful to reset an accumulator to start fresh. Here’s a simple Reset method:

func (a *Accumulator[T]) Reset() {
 var zero T
 a.Sum = zero
 a.Count = 0
 a.Max = zero
 a.Min = zero
}

This method resets all the fields in the accumulator to their zero values.

Putting It All Together

Here’s the full code for our generic accumulator:

package main

import "golang.org/x/exp/constraints"

type Numeric interface {
 constraints.Integer | constraints.Float
}

type Accumulator[T Numeric] struct {
 Sum   T
 Count float64
 Max   T
 Min   T
}

func NewAccumulator[T Numeric]() *Accumulator[T] {
 var zero T
 return &Accumulator[T]{
  Sum:   zero,
  Count: 0,
  Max:   zero,
  Min:   zero,
 }
}

func (a *Accumulator[T]) Add(value T) {
 a.AddC(value, 1)
}

func (a *Accumulator[T]) Reset() {
 var zero T
 a.Sum = zero
 a.Count = 0
 a.Max = zero
 a.Min = zero
}

func (a *Accumulator[T]) AddC(value T, count float64) {
 if a.Count == 0 {
  a.Max = value
  a.Min = value
 } else {
  if value > a.Max {
   a.Max = value
  }
  if value < a.Min {
   a.Min = value
  }
 }
 a.Sum += value
 a.Count += count
}

func (a *Accumulator[T]) Avg() T {
 if a.Count == 0 {
  var zero T
  return zero
 }
 return a.Sum / T(a.Count)
}

Using the Generic Accumulator

Let’s see how we can use the Accumulator for different numeric types:

package main

import (
 "fmt"
)

func main() {
 // Integer accumulator example
 intAcc := NewAccumulator[int]()
 intAcc.Add(10)
 intAcc.Add(20)
 intAcc.Add(5)
 fmt.Printf("Integer Accumulator - Sum: %d, Count: %.0f, Max: %d, Min: %d, Avg: %d\n", intAcc.Sum, intAcc.Count, intAcc.Max, intAcc.Min, intAcc.Avg())

 // Float accumulator example
 floatAcc := NewAccumulator[float64]()
 floatAcc.Add(10.5)
 floatAcc.Add(20.3)
 floatAcc.Add(5.1)
 fmt.Printf("Float Accumulator - Sum: %.2f, Count: %.0f, Max: %.2f, Min: %.2f, Avg: %.2f\n", floatAcc.Sum, floatAcc.Count, floatAcc.Max, floatAcc.Min, floatAcc.Avg())
}

Output:

Integer Accumulator - Sum: 35, Count: 3, Max: 20, Min: 5, Avg: 11
Float Accumulator - Sum: 35.90, Count: 3, Max: 20.30, Min: 5.10, Avg: 11.97

In the example above, we create two accumulators: one for integers and one for floating-point numbers. Both accumulators work seamlessly due to the use of Go’s generic type parameters.

Conclusion

Go’s support for generics makes it possible to write cleaner, more flexible, and reusable code. In this article, we’ve explored how to build a generic accumulator that works with both integers and floating-point numbers. With just a few lines of code, we can now accumulate sums, counts, averages, and more without writing separate implementations for different types.

This is just the beginning of what you can do with generics in Go. Whether you’re building libraries or complex systems, generics help reduce redundancy and improve code maintainability.

Newsletter
No spam. Just the latest releases and tips, interesting articles, and exclusive interviews in your inbox every week.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.