Generated banner for Golang Design Patterns
Generated logo for Golang Design Patterns

Golang Design Patterns

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

Idiomatic Golang design patterns — functional options, constructor APIs, `init()` and global-state avoidance, enums, panic vs error decisions, resource management and lifecycle, graceful shutdown, timeouts and retries, streaming and iterators, and architecture styles (clean, hexagonal, DDD, flat). Apply when choosing between architectural patterns, implementing functional options, designing constructor APIs, setting up graceful shutdown, applying resilience patterns, or asking which idiomatic Go pattern fits a specific problem. Not for wiring a DI container or comparing DI libraries (→ See `samber/cc-skills-golang@golang-dependency-injection` skill), nor for error wrapping, `errors.Is`/`As`, or logging mechanics (→ See `samber/cc-skills-golang@golang-error-handling` skill).
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a comprehensive guide to idiomatic Go development. It emphasizes simplicity, explicitness, and testability, offering patterns for constructor APIs, resource management, error handling, and architectural organization. It helps developers choose the right level of abstraction and structure for their projects, ranging from simple scripts to complex, domain-driven services, while avoiding common pitfalls like global state and premature optimization.

What it does

  • Provides idiomatic patterns for constructors, enums, and resource lifecycle management.
  • Offers guidance on choosing architectural styles like Clean, Hexagonal, or DDD based on project scale.
  • Defines best practices for resilience, including timeouts, retries, and resource limiting.
  • Advises on Go-specific idioms like functional options, interface checks, and avoiding init() functions.
  • Supports code reviews by identifying common design issues like unbounded resources or implicit dependencies.
  • Promotes a 'fail-fast' approach and domain purity to ensure maintainable business logic.

When to use it

  • When designing new Go APIs, packages, or application structures.
  • When deciding on an architectural pattern for a new service.
  • When auditing existing code for design flaws, hidden dependencies, or resource leaks.
  • When looking for idiomatic ways to implement resilience, configuration, or data processing.
  • When you need to ensure your Go code remains testable and decoupled from infrastructure.

Advantages

  • Promotes consistent, idiomatic code across the codebase.
  • Reduces technical debt by discouraging premature abstraction and hidden dependencies.
  • Improves testability through dependency injection and interface-based design.
  • Enhances system reliability with clear patterns for timeouts, retries, and graceful shutdowns.
  • Scales architectural complexity appropriately to the project's actual needs.

Usage examples

“How should I structure a constructor for a new service that needs several optional configurations?”
The agent will recommend the functional options pattern, providing a code example that demonstrates how to define an Option type and a variadic constructor for clean, extensible API design.
“I am building a medium-sized service; what architecture should I use?”
The agent will ask about the project's scope and complexity, then suggest a layered architecture (handler/service/repository) or, if appropriate, a more formal approach like Clean or Hexagonal architecture.
“Review this code for common Go design issues.”
The agent will scan the provided code for anti-patterns such as init() abuse, missing timeouts on external calls, lack of resource cleanup, or unbounded resource usage.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/samber/golang-design-patterns/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-design-patterns.zip -d .agents/skills

Documentation by samber

Persona: You are a Go architect who values simplicity and explicitness. You apply patterns only when they solve a real problem — not to demonstrate sophistication — and you push back on premature abstraction.

Modes:

  • Design mode — creating new APIs, packages, or application structure: ask the developer about their architecture preference before proposing patterns; favor the smallest pattern that satisfies the requirement.

  • Review mode — auditing existing code for design issues: scan for init() abuse, unbounded resources, missing timeouts, and implicit global state; report findings before suggesting refactors.

Community default. A company skill that explicitly supersedes samber/cc-skills-golang@golang-design-patterns skill takes precedence.

Go Design Patterns & Idioms

Idiomatic Go patterns for production-ready code. For error handling details see the samber/cc-skills-golang@golang-error-handling skill; for context propagation see samber/cc-skills-golang@golang-context skill; for struct/interface design see samber/cc-skills-golang@golang-structs-interfaces skill.

Best Practices Summary

  1. Constructors SHOULD use functional options — they scale better as APIs evolve (one function per option, no breaking changes)

  2. Functional options MUST return an error if validation can fail — catch bad config at construction, not at runtime

  3. Avoid init() — runs implicitly, cannot return errors, makes testing unpredictable. Use explicit constructors

  4. Enums SHOULD start at 1 (or Unknown sentinel at 0) — Go's zero value silently passes as the first enum member

  5. Error cases MUST be handled first with early return — keep happy path flat

  6. Panic is for bugs, not expected errors — callers can handle returned errors; panics crash the process

  7. defer Close() immediately after opening — later code changes can accidentally skip cleanup

  8. runtime.AddCleanup over runtime.SetFinalizer — finalizers are unpredictable and can resurrect objects

  9. Every external call SHOULD have a timeout — a slow upstream hangs your goroutine indefinitely

  10. Limit everything (pool sizes, queue depths, buffers) — unbounded resources grow until they crash

  11. Retry logic MUST check context cancellation between attempts

  12. Use strings.Builder for concatenation in loops → see samber/cc-skills-golang@golang-code-style

  13. string vs []byte: use []byte for mutation and I/O, string for display and keys — conversions allocate

  14. Iterators (Go 1.23+): use for lazy evaluation — avoid loading everything into memory

  15. Stream large transfers — loading millions of rows causes OOM; stream keeps memory constant

  16. //go:embed for static assets — embeds at compile time, eliminates runtime file I/O errors

  17. Use crypto/rand for keys/tokens — math/rand is predictable → see samber/cc-skills-golang@golang-security

  18. Regexp MUST be compiled once at package level — compilation is O(n) and allocates

  19. Compile-time interface checks: var _ Interface = (*Type)(nil)

  20. A little recode > a big dependency — each dep adds attack surface and maintenance burden

  21. Design for testability — accept interfaces, inject dependencies

Constructor Patterns: Functional Options vs Builder

Functional Options (Preferred)
type Server struct {
    addr         string
    readTimeout  time.Duration
    writeTimeout time.Duration
    maxConns     int
}

type Option func(*Server)

func WithReadTimeout(d time.Duration) Option {
    return func(s *Server) { s.readTimeout = d }
}

func WithWriteTimeout(d time.Duration) Option {
    return func(s *Server) { s.writeTimeout = d }
}

func WithMaxConns(n int) Option {
    return func(s *Server) { s.maxConns = n }
}

func NewServer(addr string, opts ...Option) *Server {
    // Default options
    s := &Server{
        addr:         addr,
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        maxConns:     100,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
srv := NewServer(":8080",
    WithReadTimeout(30*time.Second),
    WithMaxConns(500),
)

Constructors SHOULD use functional options — they scale better with API evolution and require less code. Use builder pattern only if you need complex validation between configuration steps.

Constructors & Initialization

Avoid init() and Mutable Globals

init() runs implicitly, makes testing harder, and creates hidden dependencies:

  • Multiple init() functions run in declaration order, across files in filename alphabetical order — fragile

  • Cannot return errors — failures must panic or log.Fatal

  • Runs before main() and tests — side effects make tests unpredictable

// Bad — hidden global state
var db *sql.DB

func init() {
    var err error
    db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        log.Fatal(err)
    }
}

// Good — explicit initialization, injectable
func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}
Enums: Start at 1

Zero values should represent invalid/unset state:

type Status int

const (
    StatusUnknown Status = iota // 0 = invalid/unset
    StatusActive                // 1
    StatusInactive              // 2
    StatusSuspended             // 3
)
Compile Regexp Once
// Good — compiled once at package level
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

func ValidateEmail(email string) bool {
    return emailRegex.MatchString(email)
}
Use //go:embed for Static Assets
import "embed"

//go:embed templates/*
var templateFS embed.FS

//go:embed version.txt
var version string
Compile-Time Interface Checks

→ See samber/cc-skills-golang@golang-structs-interfaces for the var _ Interface = (*Type)(nil) pattern.

Error Flow Patterns

Error cases MUST be handled first with early return — keep the happy path at minimal indentation. → See samber/cc-skills-golang@golang-code-style for the full pattern and examples.

When to Panic vs Return Error
  • Return error: network failures, file not found, invalid input — anything a caller can handle

  • Panic: nil pointer in a place that should be impossible, violated invariant, Must* constructors used at init time

  • .Close() / Flush() errors: read-only cleanup can often use defer f.Close(), but write/flush resources must report close or flush errors when durability matters

Data Handling

string vs []byte vs []rune
TypeDefault forUse when
stringEverythingImmutable, safe, UTF-8
[]byteI/OWriting to io.Writer, building strings, mutations
[]runeUnicode opslen() must mean characters, not bytes

Avoid repeated conversions — each one allocates. Stay in one type until you need the other.

Iterators & Streaming for Large Data

Use iterators (Go 1.23+) and streaming patterns to process large datasets without loading everything into memory. For large transfers between services (e.g., 1M rows DB to HTTP), stream to prevent OOM.

For code examples, see Data Handling Patterns.

Resource Management

defer Close() immediately after opening — don't wait, don't forget:

f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close() // right here, not 50 lines later

rows, err := db.QueryContext(ctx, query)
if err != nil {
    return err
}
defer rows.Close()

For graceful shutdown, resource pools, and runtime.AddCleanup, see Resource Management.

Resilience & Limits

Timeout Every External Call
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

resp, err := httpClient.Do(req.WithContext(ctx))
Retry & Context Checks

Retry logic MUST check ctx.Err() between attempts and use exponential/linear backoff via select on ctx.Done(). Long loops MUST check ctx.Err() periodically. → See samber/cc-skills-golang@golang-context skill.

Database Patterns

→ See samber/cc-skills-golang@golang-database skill for sqlx/pgx, transactions, nullable columns, connection pools, repository interfaces, testing.

Architecture

Ask the developer which architecture they prefer: clean architecture, hexagonal, DDD, or flat layout. Don't impose complex architecture on a small project.

Core principles regardless of architecture:

  • Keep domain pure — no framework dependencies in the domain layer

  • Fail fast — validate at boundaries, trust internal code

  • Make illegal states unrepresentable — use types to enforce invariants

  • Respect 12-factor app principles — → see samber/cc-skills-golang@golang-project-layout

Detailed Guides

GuideScope
Architecture PatternsHigh-level principles, when each architecture fits
Clean ArchitectureUse cases, dependency rule, layered adapters
Hexagonal ArchitecturePorts and adapters, domain core isolation
Domain-Driven DesignAggregates, value objects, bounded contexts

Code Philosophy

  • Avoid repetitive code — but don't abstract prematurely

  • Minimize dependencies — a little recode > a big dependency

  • Design for testability — accept interfaces, inject dependencies, keep functions pure

Cross-References

  • → See samber/cc-skills-golang@golang-data-structures skill for data structure selection, internals, and container/ packages

  • → See samber/cc-skills-golang@golang-error-handling skill for error wrapping, sentinel errors, and the single handling rule

  • → See samber/cc-skills-golang@golang-structs-interfaces skill for interface design and composition

  • → See samber/cc-skills-golang@golang-concurrency skill for goroutine lifecycle and graceful shutdown

  • → See samber/cc-skills-golang@golang-context skill for timeout and cancellation patterns

  • → See samber/cc-skills-golang@golang-project-layout skill for architecture and directory structure

  • → See samber/cc-skills-golang@golang-refactoring skill for safely staging a migration toward one of these patterns (options struct, DI, consumer-side interfaces) across an existing codebase

Files

references/clean-architecture.md

Clean Architecture in Go

Table of Contents

  • When to Use

  • The Dependency Rule

  • Project Structure

  • Code Examples

    • Entity — pure domain logic, zero dependencies

    • Use Case — orchestrates business operations

    • Adapter — implements a port

    • Handler — translates HTTP to use case calls

  • Key Principle

  • Wiring

When to Use

Apply clean architecture when you need strong separation between business logic and infrastructure — typically medium-to-large services (2K+ lines) where testability, framework independence, and clear dependency direction matter. Do NOT use for small CLI tools or scripts.

The Dependency Rule

Dependencies point inward only. Inner layers never import outer layers.

Frameworks & Drivers  →  Interface Adapters  →  Use Cases  →  Entities
(HTTP, DB, gRPC)         (handlers, repos)      (app logic)   (domain)

Each layer defines interfaces for what it needs. Outer layers implement those interfaces.

Project Structure

order-service/
├── cmd/
│   └── server/
│       └── main.go                  # Wiring only — builds the dependency graph
├── internal/
│   ├── entity/
│   │   ├── order.go                 # Order entity + business rules
│   │   ├── item.go                  # OrderItem
│   │   └── status.go                # OrderStatus enum
│   ├── order/
│   │   ├── place.go                 # PlaceOrderUseCase
│   │   ├── cancel.go                # CancelOrderUseCase
│   │   └── port.go                  # Interfaces this use case depends on
│   ├── adapter/
│   │   ├── handler/
│   │   │   └── order_handler.go    # HTTP handler — calls use cases
│   │   ├── repository/
│   │   │   └── order_postgres.go   # OrderRepository — implements port
│   │   └── gateway/
│   │       └── payment_client.go   # External payment API client
│   └── infrastructure/
│       ├── router.go               # HTTP router setup
│       ├── database.go             # DB connection
│       └── config.go               # Config loading
├── go.mod
└── go.sum

Code Examples

Entity — pure domain logic, zero dependencies
// internal/entity/order.go
package entity

type Order struct {
    ID     string
    Items  []Item
    Status OrderStatus
}

func (o *Order) Cancel() error {
    if o.Status == StatusShipped {
        return ErrCannotCancelShipped
    }
    o.Status = StatusCancelled
    return nil
}

func (o *Order) Total() int64 {
    var sum int64
    for _, item := range o.Items {
        sum += item.Price * int64(item.Quantity)
    }
    return sum
}
Use Case — orchestrates business operations
// internal/order/port.go
package order

// Ports — interfaces defined by the use case, implemented by adapters
type OrderRepository interface {
    Save(ctx context.Context, order *entity.Order) error
    FindByID(ctx context.Context, id string) (*entity.Order, error)
}

type PaymentGateway interface {
    Charge(ctx context.Context, orderID string, amount int64) error
}
// internal/order/place.go
package order

type PlaceOrderUseCase struct {
    orders   OrderRepository
    payments PaymentGateway
}

func NewPlaceOrderUseCase(orders OrderRepository, payments PaymentGateway) *PlaceOrderUseCase {
    return &PlaceOrderUseCase{orders: orders, payments: payments}
}

func (uc *PlaceOrderUseCase) Execute(ctx context.Context, orderID string) error {
    order, err := uc.orders.FindByID(ctx, orderID)
    if err != nil {
        return fmt.Errorf("finding order: %w", err)
    }

    if err := uc.payments.Charge(ctx, order.ID, order.Total()); err != nil {
        return fmt.Errorf("charging payment: %w", err)
    }

    order.Status = entity.StatusPlaced
    return uc.orders.Save(ctx, order)
}
Adapter — implements a port
// internal/adapter/repository/order_postgres.go
package repository

type OrderPostgres struct {
    db *sql.DB
}

func NewOrderPostgres(db *sql.DB) *OrderPostgres {
    return &OrderPostgres{db: db}
}

func (r *OrderPostgres) FindByID(ctx context.Context, id string) (*entity.Order, error) {
    // SQL query, scan into entity.Order
}

func (r *OrderPostgres) Save(ctx context.Context, order *entity.Order) error {
    // SQL upsert
}
Handler — translates HTTP to use case calls
// internal/adapter/handler/order_handler.go
package handler

type OrderHandler struct {
    placeOrder *usecase.PlaceOrderUseCase
}

func (h *OrderHandler) HandlePlaceOrder(w http.ResponseWriter, r *http.Request) {
    orderID := chi.URLParam(r, "id")

    if err := h.placeOrder.Execute(r.Context(), orderID); err != nil {
        // Map domain errors to HTTP status codes
        http.Error(w, err.Error(), mapToHTTPStatus(err))
        return
    }

    w.WriteHeader(http.StatusOK)
}

Key Principle

Interfaces live where they are consumed, not where they are implemented. The usecase/order/port.go file defines OrderRepository — the adapter in adapter/repository/ implements it. This keeps the use case layer free from infrastructure imports.

Wiring

All dependency construction happens in cmd/server/main.go. → See samber/cc-skills-golang@golang-dependency-injection skill for DI library alternatives.

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-design-patterns · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-design-patterns.zip