Functional options
Use functional options when a constructor has many optional settings and defaults should stay readable.
Canonical guidance
- prefer simple constructors until option count justifies more structure
- use functional options when defaults matter and optional knobs keep growing
- validate options in one place during construction
Use when
- clients with many optional settings
- APIs that need forward-compatible configuration growth
- constructors that would otherwise take many zero values
Avoid
- using functional options for one or two obvious parameters
- mutating live objects through reused option functions
- hiding required dependencies as optional knobs
Preferred pattern
type Option func(*Client)
func WithTimeout(d time.Duration) Option {
return func(c *Client) { c.timeout = d }
}
func NewClient(addr string, opts ...Option) *Client {
c := &Client{addr: addr, timeout: 5 * time.Second}
for _, opt := range opts {
opt(c)
}
return c
}
Anti-pattern
- constructors that require callers to pass long runs of zero values or
nilplaceholders
Explanation: This anti-pattern is tempting because positional parameters are simple at first, but optional configuration scales poorly once defaults and growth matter.
Why
- functional options can preserve readable call sites while keeping defaults centralized
Related pages
Sources
- Not using the functional options pattern (#11) - Teiva Harsanyi