| name | golang-patterns |
| description | Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications. |
Go Development Patterns
Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.
When to Activate
- Writing new Go code
- Reviewing Go code
- Refactoring existing Go code
- Designing Go packages/modules
Core Principles
1. Simplicity and Clarity
Go favors simplicity over cleverness. Code should be obvious and easy to read.
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}
2. Make the Zero Value Useful
Design types so their zero value is immediately usable without initialization.
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
var buf bytes.Buffer
buf.WriteString("hello")
type BadCounter struct {
counts map[string]int
}
3. Accept Interfaces, Return Structs
Functions should accept interface parameters and return concrete types.
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
func ProcessData(r io.Reader) (io.Reader, error) {
}
Error Handling Patterns
Error Wrapping with Context
Use fmt.Errorf with %w to wrap errors and preserve the error chain:
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
For custom error types and error checking patterns, see references/code-examples.md.
Concurrency Patterns
Worker Pool
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
For context cancellation, graceful shutdown, errgroup, and goroutine leak prevention patterns, see references/code-examples.md.
Context as First Parameter
Always pass context.Context as the first parameter of a function:
func ProcessRequest(ctx context.Context, id string) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
}
## Interface Design
### Small, Focused Interfaces
```go
// Single-method interfaces compose cleanly
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// Compose interfaces as needed
type ReadWriteCloser interface {
Reader
Writer
Closer
}
For patterns on defining interfaces where they're used and optional behavior with type assertions, see references/code-examples.md.
Package Organization
Standard Project Layout
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point
├── internal/
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
├── pkg/
│ └── client/ # Public API client
├── api/
│ └── v1/ # API definitions (proto, OpenAPI)
├── testdata/ # Test fixtures
├── go.mod
├── go.sum
└── Makefile
Package Naming
package http
package json
package user
package httpHandler
package json_parser
package userService
Avoid Package-Level State
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}
Struct Design
Functional Options Pattern
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second,
logger: log.Default(),
}
for _, opt := range opts {
opt(s)
}
return s
}
For embedding patterns, see references/code-examples.md.
Memory and Performance
Preallocate Slices When Size is Known
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}
For sync.Pool and string concatenation patterns, see references/code-examples.md.
Go Tooling Integration
Essential Commands
go build ./...
go run ./cmd/myapp
go test ./...
go test -race ./...
go test -cover ./...
go vet ./...
staticcheck ./...
golangci-lint run
go mod tidy
go mod verify
gofmt -w .
goimports -w .
Recommended Linter Configuration (.golangci.yml)
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: false
Quick Reference: Go Idioms
| Idiom | Description |
|---|
| Accept interfaces, return structs | Functions accept interface params, return concrete types |
| Errors are values | Treat errors as first-class values, not exceptions |
| Don't communicate by sharing memory | Use channels for coordination between goroutines |
| Make the zero value useful | Types should work without explicit initialization |
| A little copying is better than a little dependency | Avoid unnecessary external dependencies |
| Clear is better than clever | Prioritize readability over cleverness |
| gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports |
| Return early | Handle errors first, keep happy path unindented |
Anti-Patterns to Avoid
Avoid naked returns in long functions — they obscure what is being returned. Use explicit returns:
func process() (int, error) {
return result, nil
}
Avoid using panic for control flow. Return errors instead:
func GetUser(id string) (*User, error) {
user, err := db.Find(id)
if err != nil {
return nil, err
}
return user, nil
}
Avoid mixing value and pointer receivers on the same type. Pick one style and be consistent.
Prefer context as the first parameter, not inside struct fields.
Go Version Notes (June 2026)
Current: Go 1.26 (Feb 2026). Key recent additions:
- Go 1.26: Self-referential generics,
new with expressions, experimental runtime/secret
- Go 1.25: Removed "core types" concept, experimental
encoding/json/v2, PGO stabilized
- Go 1.24: Generic type aliases,
os.Root for directory-limited filesystem, Swiss Tables for maps