Go

Explain the functional options pattern. Compare it with a config struct and a builder. How do you validate options and return errors?

Question 548MediumGo 1.22 to 1.25

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

All 16 Go Idioms, Design Patterns & Language Design questions