| name | golang-patterns |
| description | Idiomatic Golang design patterns: functional options, API constructors, struct and interface guidelines, error handling flow, resource lifecycle, performance optimizations, and testability. Apply when refactoring, writing new APIs, designing packages, or deciding on structural patterns. |
| user-invocable | true |
| license | MIT |
| compatibility | Designed for AI coding agents and Go developers. |
Go Design Patterns & Idioms
This workspace skill documents structural, behavioral, and idiomatic patterns for Go applications, aiming to enforce codebase cleanliness, performance, and predictability.
1. API & Constructor Patterns
Functional Options (Preferred)
Use Functional Options for constructors when configuration parameters can grow or change. This prevents breaking API changes in the future.
type Client struct {
endpoint string
timeout time.Duration
maxConns int
}
type Option func(*Client)
func WithTimeout(t time.Duration) Option {
return func(c *Client) {
c.timeout = t
}
}
func WithMaxConns(n int) Option {
return func(c *Client) {
c.maxConns = n
}
}
func NewClient(endpoint string, opts ...Option) *Client {
c := &Client{
endpoint: endpoint,
timeout: 30 * time.Second,
maxConns: 10,
}
for _, opt := range opts {
opt(c)
}
return c
}
Constructor Design
- Validation: If validation is required, the constructor MUST return
(*T, error) rather than failing at runtime.
- Explicitness: Avoid using global structures or singletons. Inject dependencies through the constructor.
- Avoid
init(): Avoid using init() functions for setup. They hide package-level side-effects, cannot return errors, and make tests unpredictable. Use explicit, user-invoked initialization functions.
2. Struct and Interface Design
- Accept Interfaces, Return Concrete Types:
- Functions should accept interface parameters (maximizing reuse) and return concrete structs (making downstream access simpler).
- Keep Interfaces Small:
- Prefer single-method interfaces (e.g.,
io.Reader, io.Writer) or interfaces with 2-3 methods. Large interfaces are hard to implement and mock.
- Compile-Time Interface Verification:
- Enums:
3. Error Handling and Control Flow
- Early Returns (Line of Sight):
- Handle error conditions immediately and return. Keep the happy path flat (un-indented).
- Contextual Wrap:
- Panic is NOT for Error Flow:
- Only panic for unrecoverable programmer errors (e.g., array index out of bounds, nil pointer dereference, setup bugs). Never use panics for expected edge cases.
4. Lifecycles and Resource Management
- Deallocate / Close Immediately:
- Timeouts Everywhere:
- Every external network request, channel select, and context block MUST have a timeout or context deadline. Unbounded resources will eventually block the whole system.
- Graceful Shutdown:
- Pass a
context.Context to service runners, listening to ctx.Done() for graceful termination.
5. Performance and Allocation Rules
- Strings Concatenation:
- Use
strings.Builder instead of + when concatenating strings in loops to minimize allocations.
- Pre-allocate slices:
- When the size of a slice is known beforehand, initialize it using
make([]T, 0, capacity) to avoid reallocation overhead.
- Regex Compilation:
- Never compile regexes inside functions that run repeatedly. Compile them once using
regexp.MustCompile at package-level.