I Don’t Like the Builder Pattern (in Go)
A lot of Go libraries these days utilise the builder pattern as a way to be sugary and cutesy. I don’t really have anything against the builder pattern in general - in some languages it works well, but I don’t think Go is one of them.
Take a look at this snippet I wrote recently that uses the charmbracelet/huh.
huh.NewInput().
Title(fmt.Sprintf("Test question: %s", question.Question)).
Description("Enter the answer to the test question").
EchoMode(huh.EchoModePassword).
Validate(func(s string) error {
if s != question.Answer {
return fmt.Errorf("incorrect answer")
}
return nil
})
In Go there’s only one alternative way of laying this code out: chain all of the calls on one line – which is inevitably long and hideous:
huh.NewInput().Title(fmt.Sprintf("Test question: %s", question.Question)).Description("Enter the answer to the test question").EchoMode(huh.EchoModePassword).Validate(func(s string) error {
if s != question.Answer {
return fmt.Errorf("incorrect answer")
}
return nil
})
Whilst the first option is obviously more readable, it’s clumsy to write. The problem with the first option is that the accessor (.) must be before the line break, unlike how it would be typically written in Java or JavaScript where it may come after:
new Input()
.title(`Test question: ${question.question}`)
.description('Enter the answer to the test question')
.echoMode('password')
.validate((value) => {
if (value !== question.answer) {
throw new Error('Incorrect answer');
}
})
.build();
To use language server suggestions, after each line you type the ., select an autocomplete suggestion, then must move the cursor left again back to before the . and insert a line break.
Here’s what I mean:

Or you cram everything on to one line and insert the line breaks after, either way it’s unpleasant to write.
Sometimes the type implements pointer receivers, which makes the builder pattern optional. I think this is ideal because it gives the consumer freedom to use the style they prefer.
The previous example using charmbracelet/huh can indeed be written alternatively without the builder pattern.
input := huh.NewInput()
input.Title(fmt.Sprintf("Test question: %s", question.Question))
input.Description("Enter the answer to the test question")
input.EchoMode(huh.EchoModePassword)
input.Validate(func(s string) error {
if s != question.Answer {
return fmt.Errorf("incorrect answer")
}
return nil
})
I’m a bigger fan of the functional options pattern. It works more nicely with the language server and gofmt. The only downside I’m aware of is that it can be slow – which doesn’t really matter unless you’re using it in a hot function.
huh.NewInput(
huh.WithTitle(fmt.Sprintf("Test question: %s", question.Question)),
huh.WithDescription("Enter the answer to the test question"),
huh.WithEchoMode(huh.EchoModePassword),
huh.WithValidator(func(s string) error {
if s != question.Answer {
return fmt.Errorf("incorrect answer")
}
return nil
}),
)