| name | go-functional-options |
| description | Use when designing a Go constructor or factory function with optional configuration — especially with 3+ optional parameters or extensible APIs. Also use when building a New* function that takes many settings, even if they don't mention "functional options" by name. Does not cover general function design (see go-functions). |
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
type options struct {
field1 Type1
field2 Type2
}
type Option interface {
apply(*options)
}
type field1Option Type1
func (o field1Option) apply(opts *options) { opts.field1 = Type1(o) }
func WithField1(v Type1) Option { return field1Option(v) }
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 when designing the
Option interface or choosing between interface and closure approaches
- Naming conventions: See go-naming when naming
With* constructors, option types, or the unexported options struct
- Function design: See go-functions when organizing constructors within a file or formatting variadic signatures
- Documentation: See go-documentation when documenting
Option types, With* functions, or constructor behavior
External Resources