Skillshub golang-design-patterns

Idiomatic Golang design patterns — functional options, constructors, error flow and cascading, resource management and lifecycle, graceful shutdown, resilience, architecture, dependency injection, data handling, and streaming. Apply when designing Go APIs, structuring applications, choosing between patterns, making design decisions, architectural choices, or production hardening.

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/Harmeet10000/skills/golang-design-patterns" ~/.claude/skills/comeonoliver-skillshub-golang-design-patterns && rm -rf "$T"
manifest: skills/Harmeet10000/skills/golang-design-patterns/SKILL.md
source content

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()
    errors
    : acceptable to not check —
    defer f.Close()
    is fine without error handling

Data Handling

string vs []byte vs []rune

TypeDefault forUse when
string
EverythingImmutable, safe, UTF-8
[]byte
I/OWriting to
io.Writer
, building strings, mutations
[]rune
Unicode ops
len()
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