Skip to main content
โšก Calmops

Modern Go Programming: Advanced Patterns and Best Practices

Introduction

Go continues to be one of the most popular languages for building cloud-native applications, APIs, and distributed systems. With the introduction of generics in Go 1.18 and continuous improvements in tooling, the language has evolved significantly.

In 2026, Go remains the language of choice for Kubernetes, Docker, and many other cloud infrastructure tools. This guide covers advanced Go patterns and best practices that will help you write production-quality Go code.

Generics

Type Parameters

// Generic function
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Generic type
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

Constraints

// Using interfaces as constraints
type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

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

// Comparable constraint
func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

// Custom constraints
type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
    ~float32 | ~float64 | ~string
}

func BinarySearch[T Ordered](slice []T, target T) int {
    lo, hi := 0, len(slice)-1
    for lo <= hi {
        mid := (lo + hi) / 2
        if slice[mid] < target {
            lo = mid + 1
        } else if slice[mid] > target {
            hi = mid - 1
        } else {
            return mid
        }
    }
    return -1
}

Concurrency Patterns

Context with Cancellation

func fetchAllData(ctx context.Context) ([]Result, error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    
    results := make(chan Result, 10)
    errs := make(chan error, 3)
    
    var wg sync.WaitGroup
    
    // Fetch from multiple sources concurrently
    wg.Add(3)
    go func() { defer wg.Done(); errs <- fetchUsers(ctx, results) }()
    go func() { defer wg.Done(); errs <- fetchOrders(ctx, results) }()
    go func() { defer wg.Done(); errs <- fetchProducts(ctx, results) }()
    
    // Close results when done
    go func() {
        wg.Wait()
        close(results)
        close(errs)
    }()
    
    // Collect results
    var allResults []Result
    for r := range results {
        allResults = append(allResults, r)
    }
    
    // Check for errors
    for err := range errs {
        if err != nil {
            return nil, err
        }
    }
    
    return allResults, nil
}

Worker Pool Pattern

func workerPool(jobs <-chan Job, results chan<- Result, workers int) {
    var wg sync.WaitGroup
    
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- processJob(job)
            }
        }()
    }
    
    wg.Wait()
    close(results)
}

func processJob(job Job) Result {
    // Simulate work
    time.Sleep(time.Millisecond * 100)
    return Result{ID: job.ID, Success: true}
}

Pipeline Pattern

func pipeline(data []int) <-chan int {
    out := make(chan int, len(data))
    go func() {
        for _, v := range data {
            out <- v
        }
        close(out)
    }()
    return out
}

func filter(in <-chan int, predicate func(int) bool) <-chan int {
    out := make(chan int)
    go func() {
        for v := range in {
            if predicate(v) {
                out <- v
            }
        }
        close(out)
    }()
    return out
}

func transform(in <-chan int, fn func(int) int) <-chan int {
    out := make(chan int)
    go func() {
        for v := range in {
            out <- fn(v)
        }
        close(out)
    }()
    return out
}

// Usage
result := transform(
    filter(
        pipeline(data),
        func(n int) bool { return n > 0 },
    ),
    func(n int) int { return n * 2 },
)

Error Handling

Wrapped Errors

import "fmt"

func readConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("reading config: %w", err)
    }
    
    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return nil, fmt.Errorf("parsing config: %w", err)
    }
    
    return &cfg, nil
}

// Error handling with stack traces
import "github.com/pkg/errors"

func process() error {
    return errors.Wrap(err, "process failed")
}

// Custom error types
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

func validateUser(user User) error {
    if user.Name == "" {
        return &ValidationError{Field: "name", Message: "required"}
    }
    return nil
}

Error Handling Patterns

// Result type pattern
type Result[T any] struct {
    Value T
    Error error
}

func safeCall[T any](fn func() (T, error)) Result[T] {
    v, err := fn()
    return Result[T]{Value: v, Error: err}
}

// Usage
result := safeCall(func() (User, error) {
    return fetchUser(id)
})
if result.Error != nil {
    log.Error(result.Error)
    return
}
user := result.Value

Testing

Table-Driven Tests

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "[email protected]", false},
        {"invalid email", "test@", true},
        {"empty email", "", true},
        {"missing @", "testexample.com", true},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}

Mocking

// Interface for dependency
type UserRepository interface {
    GetUser(id string) (User, error)
    SaveUser(user User) error
}

// Real implementation
type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) GetUser(id string) (User, error) {
    // Actual DB query
}

// Mock implementation for testing
type MockUserRepository struct {
    Users map[string]User
    GetUserFunc func(id string) (User, error)
}

func (m *MockUserRepository) GetUser(id string) (User, error) {
    if m.GetUserFunc != nil {
        return m.GetUserFunc(id)
    }
    if user, ok := m.Users[id]; ok {
        return user, nil
    }
    return User{}, sql.ErrNoRows
}

func TestGetUser(t *testing.T) {
    mock := &MockUserRepository{
        Users: map[string]User{
            "1": {ID: "1", Name: "John"},
        },
    }
    
    service := NewUserService(mock)
    user, err := service.GetUser("1")
    
    assert.NoError(t, err)
    assert.Equal(t, "John", user.Name)
}

Property-Based Testing

import "testing/quick"

func TestSortReversal(t *testing.T) {
    f := func(s []int) bool {
        // Property: sorting twice should give same result
        sorted := make([]int, len(s))
        copy(sorted, s)
        Sort(sorted)
        
        reversed := make([]int, len(sorted))
        copy(reversed, sorted)
        Sort(reversed)
        
        return reflect.DeepEqual(sorted, reversed)
    }
    
    if err := quick.Check(f, nil); err != nil {
        t.Error(err)
    }
}

Project Structure

Standard Layout

myproject/
โ”œโ”€โ”€ cmd/
โ”‚   โ””โ”€โ”€ myapp/
โ”‚       โ””โ”€โ”€ main.go
โ”œโ”€โ”€ internal/
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ””โ”€โ”€ config.go
โ”‚   โ”œโ”€โ”€ handler/
โ”‚   โ”‚   โ””โ”€โ”€ handler.go
โ”‚   โ”œโ”€โ”€ service/
โ”‚   โ”‚   โ””โ”€โ”€ service.go
โ”‚   โ””โ”€โ”€ repository/
โ”‚       โ””โ”€โ”€ repository.go
โ”œโ”€โ”€ pkg/
โ”‚   โ””โ”€โ”€ utils/
โ”‚       โ””โ”€โ”€ utils.go
โ”œโ”€โ”€ api/
โ”‚   โ””โ”€โ”€ openapi.yaml
โ”œโ”€โ”€ configs/
โ”‚   โ””โ”€โ”€ config.yaml
โ”œโ”€โ”€ go.mod
โ””โ”€โ”€ go.sum

Clean Architecture

// Domain layer
type User struct {
    ID    string
    Name  string
    Email string
}

type UserRepository interface {
    FindByID(id string) (User, error)
    Save(user User) error
}

// Service layer
type UserService struct {
    repo UserRepository
}

func (s *UserService) CreateUser(name, email string) (User, error) {
    user := User{
        ID:    generateID(),
        Name:  name,
        Email: email,
    }
    
    if err := s.repo.Save(user); err != nil {
        return User{}, fmt.Errorf("saving user: %w", err)
    }
    
    return user, nil
}

Performance

Profiling

import (
    "runtime/pprof"
    "os"
)

func main() {
    // CPU profiling
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()
    
    // Your code here
    doWork()
    
    // Memory profiling
    f2, _ := os.Create("mem.prof")
    pprof.WriteHeapProfile(f2)
    defer f2.Close()
}

Optimization Tips

// Use sync.Pool for frequent allocations
var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func process() {
    buf := bufferPool.Get().(*bytes.Buffer)
    defer bufferPool.Put(buf)
    
    buf.Reset()
    // Use buffer
}

// Use strings.Builder for string concatenation
var sb strings.Builder
for i := 0; i < 1000; i++ {
    sb.WriteString("item")
}
result := sb.String()

Conclusion

Go’s simplicity combined with powerful features makes it ideal for building reliable, efficient software. The patterns covered hereโ€”generics, concurrency patterns, error handling, and testingโ€”will help you write production-quality Go applications.

Remember:

  • Use generics to reduce code duplication
  • Leverage Go’s concurrency primitives
  • Handle errors explicitly
  • Write comprehensive tests
  • Profile before optimizing

Resources

Comments