SKILL.md
Functional Options Pattern
Functional options is a pattern where you declare an opaque Option type that records information in an internal struct. The constructor accepts a variadic number of these options and applies them to configure the result.
Resource Routing
references/OPTIONS-VS-STRUCTS.md - Read when choosing between config structs and functional options, implementing the full interface-based option pattern, or evaluating hybrid constructor APIs.
When to Use
Use functional options when:
- 3+ optional arguments on constructors or public APIs
- Extensible APIs that may gain new options over time
- Clean caller experience is important (no need to pass defaults)
The Pattern
Core Components
- Unexported
options struct - holds all configuration
- Exported
Option interface - with unexported apply method
- Option types - implement the interface
- **
With* constructors** - create options
Option Interface
type Option interface {
apply(*options)
}
The unexported apply method ensures only options from this package can be used.
Comparison: Functional Options vs Config Struct
| Aspect |
Functional Options |
Config Struct |
| Extensibility |
Add new With* functions |
Add new fields (may break) |
| Defaults |
Built into constructor |
Zero values or separate defaults |
| Caller experience |
Only specify what differs |
Must construct entire struct |
| Testability |
Options are comparable |
Struct comparison |
| Complexity |
More boilerplate |
Simpler setup |
Prefer Config Struct when: Fewer than 3 options, options rarely change, all options usually specified together, or internal APIs only.
Why Not Closures?
The interface approach is preferred over closure-only options because:
- Testability - Options can be compared in tests and mocks
- Debuggability - Options can implement
fmt.Stringer
- Flexibility - Options can implement additional interfaces
- Visibility - Option types are visible in documentation
Quick Reference
// 1. Unexported options struct with defaults
type options struct {
field1 Type1
field2 Type2
}
// 2. Exported Option interface, unexported method
type Option interface {
apply(*options)
}
// 3. Option type + apply + With* constructor
type field1Option Type1
func (o field1Option) apply(opts *options) { opts.field1 = Type1(o) }
func WithField1(v Type1) Option { return field1Option(v) }
// 4. Constructor applies options over defaults
func New(required string, opts ...Option) (*Thing, error) {
o := options{field1: defaultField1, field2: defaultField2}
for _, opt := range opts {
opt.apply(&o)
}
// ...
}
Checklist
Related Skills
- Interface design: See [go-interfaces](../go-interfaces/SKILL.md) when designing the
Option interface or choosing between interface and closure approaches
- Naming conventions: See [go-naming](../go-naming/SKILL.md) when naming
With* constructors, option types, or the unexported options struct
- Function design: See [go-functions](../go-functions/SKILL.md) when organizing constructors within a file or formatting variadic signatures
- Documentation: See [go-documentation](../go-documentation/SKILL.md) when documenting
Option types, With* functions, or constructor behavior
External Resources