Go Patterns Basics
10 examples to get you started with Go patterns and idioms - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Go patterns and idioms - 7 basic and 3 intermediate.
go version reports go1.26 or later)go versionCompose behavior by embedding a type with the methods you need.
type LoggingHandler struct {
http.Handler
log *slog.Logger
}
func (h LoggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.log.Info("request", "path", r.URL.Path)
h.Handler.ServeHTTP(w, r)
}ServeHTTP from the inner handlerRelated: Go Idioms: Composition Over Inheritance - why Go avoids inheritance
Declare the interface where you call the behavior, not where you implement it.
// consumer package
type Clock interface {
Now() time.Time
}
func TTL(c Clock, issued time.Time, ttl time.Duration) bool {
return c.Now().After(issued.Add(ttl))
}Clock, Store) not implementation-based (PostgresClock)Related: Constructor Functions & Package APIs - wiring dependencies through constructors
Return initialized values from New functions instead of exporting mutable structs.
type Counter struct {
n atomic.Int64
}
func NewCounter() *Counter { return &Counter{} }
func (c *Counter) Inc() { c.n.Add(1) }
func (c *Counter) Value() int64 { return c.n.Load() }New documents valid construction; zero value may be invalid for some typesRelated: Constructor Functions & Package APIs - New/Open conventions
Model optional settings as functions that mutate the target during construction.
type Option func(*Server)
func WithPort(p int) Option {
return func(s *Server) { s.port = p }
}
func NewServer(opts ...Option) *Server {
s := &Server{port: 8080}
for _, opt := range opts {
opt(s)
}
return s
}New; callers override only what they needNewServer(WithPort(3000), WithTLS(cert))Related: Functional Options Pattern - full pattern walkthrough
Wrap handlers with functions that run before and after inner logic.
func withRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := uuid.NewString()
ctx := context.WithValue(r.Context(), requestIDKey, id)
w.Header().Set("X-Request-Id", id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}func(http.Handler) http.Handler is stdlib-nativeRelated: Middleware & Decorator Patterns - chaining and ordering
Isolate storage behind an interface the service depends on.
type User struct { ID string; Email string }
type UserRepo interface {
Get(ctx context.Context, id string) (User, error)
}
type Service struct { repo UserRepo }
func (s *Service) Email(ctx context.Context, id string) (string, error) {
u, err := s.repo.Get(ctx, id)
if err != nil { return "", err }
return u.Email, nil
}UserRepo fake without a databaseRelated: Repository & Service Layer Patterns - layering guidance
Initialize expensive resources exactly once, safely under concurrency.
var (
client *http.Client
once sync.Once
)
func HTTPClient() *http.Client {
once.Do(func() {
client = &http.Client{Timeout: 10 * time.Second}
})
return client
}once.Do runs the function exactly one time across goroutinesinit() - lazy init or constructor wiring is saferRelated: sync.Once & Singleton Patterns - when globals are acceptable
Validate composed options before returning the constructed value.
func NewPool(size int, opts ...PoolOption) (*Pool, error) {
if size <= 0 {
return nil, errors.New("pool: size must be positive")
}
p := &Pool{size: size, idle: 30 * time.Second}
for _, opt := range opts {
if err := opt(p); err != nil {
return nil, err
}
}
return p, nil
}error from New signals invalid combinations earlyerror when validation is per-optionRelated: Functional Options Pattern - validation strategies
Wire a service from interfaces defined in the consumer package.
type Notifier interface {
Send(ctx context.Context, to, body string) error
}
type OrderService struct {
repo OrderRepo
notify Notifier
}
func NewOrderService(repo OrderRepo, notify Notifier) *OrderService {
return &OrderService{repo: repo, notify: notify}
}fakeNotifier and fakeRepo*OrderService; accept interfaces in parametersRelated: Repository & Service Layer Patterns - testing with fakes
Compose stdlib-compatible middleware on a chi router.
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Recoverer)
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})r.Use registers outer middleware for all routes in scopenet/http compatible - handlers are plain http.HandlerFuncRelated: Middleware & Decorator Patterns - framework-neutral chaining
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026