The functional options pattern in Go offers a flexible and readable way to configure your structs without exposing internal fields.

The Functional Options Pattern in Go


It is particularly useful when you want to provide optional configuration and there may be more configuration options in the future. This pattern is great for libraries!

Let’s see an example. Here is a super basic server without functional options:

type Server struct {
    host     string
    port     int
    protocol string
}

func NewServer(host string, port int) *Server {
    return &Server{
        host:     host,
        port:     port,
        protocol: "http",
    }
}

Over time, our requirements change, and we need to support more configuration options. Instead of changing the signature of the NewServer function, which can be problematic and not backward-compatible, we can use functional options.

Firstly, we define a functional option:

type ServerOption func(*Server)

and a function that satisfies the type:

func WithPort(port int) ServerOption {
    return func(s *Server) {
        s.port = port
    }
}

Then, we modify our NewServer function:

func NewServer(host string, opts ...ServerOption) *Server {
    server := &Server{
        host:     host,
        port:     443,  // default port set to 443
        protocol: "https",
    }

    for _, opt := range opts {
        opt(server)
    }

    return server
}

Now we can use it like this:

server1 := NewServer("localhost") // uses default port 443

server2 := NewServer("localhost", WithPort(8080))

As you can see, this allows us to offer customizations flexibly, while still maintaining readability and not exposing internal fields.

Pro Tip

Make sure that your functional options are truly optional. You’ll notice in my example that if I do not pass in WithPort(), my NewServer function returns a server with a sensible default configuration. This is good practice, and this pattern works best for things that are truly optional.

I hope you found this useful. This tip started life as a tweet (by me), and you can see the original here.