You’ll find some of these practices below, ranging from most verbose options to more parametrized, and extendable.
Using Multiple Initialization Functions

The simplest solution for dealing with a small number of variables is to use different functions. This technique uses WithX as a convention at the end of the function.

func NewAPIRequest() (*http.Request, error) {
   return http.NewRequest(http.MethodGet, ServerURI, nil)

This function creates a new HTTP request for the ServerURI which is then returned to the caller. If we want to abstract authorization without the caller knowing how it works, we can simply write the WithAuth function.

func NewRequestWithAuth(key string) (*http.Request, error) {
   req, err := NewAPIRequest()
   if err != nil {
      return nil, err
   req.Header.Add("authorization", key)
   return req, err

We are simply adding the provided key into the authorization header, however, this behavior is hidden from the caller.

  • Readable and verbose, no hidden logic behind parameters
  • Great for a small number of variations

You may also like: Google’s Go Essentials For Node.js / JavaScript Developers

Using Structs with Overriden Defaults

You can use the With notation to construct new structs the same way we create requests in the above example. Moreover, you can make parameters with default values private and access them through getters.

type Client struct {
   authKey string // private authKey set in constructors
func NewClient() *Client { // default constructor
   return &Client{}
func NewClientWithAuth(key string) *Client { // 'With' Constructor
   return &Client{key}

Now, because the caller may or may not be bringing her own key, we will want to implement a getter to be used inside our implementation.

func (c *Client) AuthKey() string {
   if c.authKey != "" {
      return c.authKey
   return "default-auth-key"

Once we have this getter, we are free to use it inside our implementations. We will either use the user-provided key or the default one. Note that you may be using e.g. local machine configuration instead of a hardcoded string here.

func (c *Client) DoSomething() {
   // using c.AuthKey() to access the authKey
   req, err := NewAPIRequestWithAuth(c.AuthKey())
   // ...


  • Great for struct initialization
  • Good for a large number of optional parameters that may not be populated by the caller but have a default value

Using Variadic Functions with Option Functions
The last approach can be seen in many libraries using struct initialization. It is widely used as a behavior-extending technique that allows users to pass either options or full functions to modify/extend the behavior of a component.

The example below builds on the Client example and allows us to pass options that modify the authKey as well as options that can modify all requests before they are sent.

type ClientOption func(c *Client)
func NewClient(opts ...ClientOption) *Client {
   client :=  &Client{}
   for _, opt := range opts {

   return client

This allows us to pass any modifier functions into the NewClient constructor. Instead of writing multiple initializers, we are now free to pass the AuthKey via the ClientOption like so:

func WithAuth(key string) ClientOption {
   // this is the ClientOption function type
   return func(c *Client) {
      c.authKey = key

Which then results in calling the constructor with the Option:


This method does not only allow us to pass primitives into the initializers. Rather full functions that can modify the internal behavior of the component. You may know this from HTTP frameworks under Middlewares. An example of such as the full implementation can be seen below:

// RequestModifier can modify the http request
type RequestModifier func(r *http.Request)

type Client struct {
   // modifiers are applied before any request
   modifiers []RequestModifier

func NewClient(opts ...RequestModifier) *Client {
   c := &Client{}
   // register the modifiers
   c.modifiers = append(c.modifiers, opts...)

   return c

// Request constructs a new http request
func (c *Client) Request() (*http.Request, error) {
   req, err := NewAPIRequest()
   if err != nil {
      return nil, err

   for _, mod := range c.modifiers {

   return req, nil

// WithAuth adds the authorization header to the req
func WithAuth(key string) RequestModifier {
   return func(r *http.Request) {
      r.Header.Add("authorization", key)

The example above allows you to pass any number of RequestModifiers into the Client struct which may have additional logic that needs to be performed before the request is returned.

  • Modifying internal behavior in libraries
  • Great for covering various unknown use-cases as middlewares do

Originally published by Peter Malina at

#go #web-development

How to dealing with Optional Parameters in Go
4 Likes10.75 GEEK