Explain the functional options pattern. Compare it with a config struct and a builder. How do you validate options and return errors?
Functional options are variadic functions of type func(*T) (or func(*T) error) applied to a value that already holds defaults. The constructor stays backward-compatible when you add options, required parameters stay positional, and each option can validate its own input.
type Server struct {
addr string
timeout time.Duration
maxConns int
}
type Option func(*Server) error
func WithTimeout(d time.Duration) Option {
return func(s *Server) error {
if d <= 0 {
return fmt.Errorf("timeout must be positive, got %v", d)
}
s.timeout = d
return nil
}
}
func WithMaxConns(n int) Option {
return func(s *Server) error { s.maxConns = n; return nil }
}
func NewServer(addr string, opts ...Option) (*Server, error) {
s := &Server{addr: addr, timeout: 30 * time.Second, maxConns: 100}
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
if s.maxConns < 1 { // checks that involve several fields go after all options are applied
return nil, errors.New("maxConns must be >= 1")
}
return s, nil
}
Config struct (New(Config{...})): simpler and easy to see, and works well with YAML or env loading. The weak spot is the zero value: you can't tell "unset" from "explicitly 0" without pointers or cmp.Or. Builder (NewBuilder().Timeout(x).Build()): common in Java but not idiomatic in Go. Errors have to be stored in the builder until Build(), and it adds a mutable type. What the interviewer wants to hear: options suit library APIs that change over time. A config struct suits app-level wiring. Validate within one option inside it, validate across fields after the loop, and return errors rather than panicking.
More on Go Idioms, Design Patterns & Language Design
- Q549How do you implement a thread-safe singleton or lazy initialization in Go? Compare sync.Once, sync.OnceValue and package-level init.
- Q550How do you model enums in Go with iota? How do you add String() via go generate stringer, validate values, and handle the zero value?
- Q551How do variadic functions work? What is the aliasing trap when passing s... to a function that modifies or appends to its argument?
- Q552Explain closures in Go: what they capture, when captured variables escape to the heap, and how to build a generator or middleware with them.
- Q553Why does Go have no inheritance, function overloading, default arguments, ternary operator or exceptions? How do idiomatic Go programs work around each?
- Q554Can you compare functions in Go? Why is a func only comparable to nil, and how does that affect using funcs as map keys?