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
Comments