The Problem
Some values are assembled through a sequence of steps, not one constructor call. When those steps live inline in handlers, the workflow becomes repetitive and the output format becomes hard to change. The same order summary may need to produce a customer-facing confirmation, an ops checklist, or an internal export.
The Solution
Builder pulls the construction steps into a dedicated object while a director coordinates the sequence. In Go, the pattern often appears as a focused interface with methods that add sections, totals, or metadata. The builder accumulates state, and the caller decides which concrete builder to use.
Structure
- Product:
CheckoutSummaryis the value being assembled. - Builder interface: Declares the steps needed to produce the summary.
- Concrete builder:
MarkdownSummaryBuilderstores output in a markdown-friendly form. - Director:
CheckoutSummaryDirectorruns the construction steps in a fixed order. - Client: Chooses the builder and asks the director to assemble the result.
Implementation
This example turns an order into a formatted checkout summary. The director always performs the same steps, while the builder owns how the final representation is assembled and rendered.
package main
import (
"fmt"
"strings"
)
type LineItem struct {
Name string
Qty int
UnitPrice int
}
type Order struct {
ID string
Customer string
Items []LineItem
}
func (o Order) Total() int {
total := 0
for _, item := range o.Items {
total += item.Qty * item.UnitPrice
}
return total
}
type CheckoutSummary struct {
Title string
Body []string
Footer string
}
func (s CheckoutSummary) Render() string {
parts := []string{s.Title}
parts = append(parts, s.Body...)
parts = append(parts, s.Footer)
return strings.Join(parts, "\n")
}
func formatMoney(cents int) string {
return fmt.Sprintf("$%.2f", float64(cents)/100)
} Best Practices
- Use Builder when construction has meaningful steps or optional sections, not just many fields.
- Keep the director optional. In Go, direct builder calls are fine when the sequence is already obvious.
- Prefer explicit step names over generic
SetFieldmethods so the workflow stays readable. - Return a final immutable value from
Buildrather than exposing the builder internals. - Do not force Builder where functional options or a plain constructor already solve the problem more simply.
When to Use
- Construction involves multiple ordered steps or reusable recipes.
- The same input flow may produce different final representations.
- You want to keep assembly logic out of transport handlers and services.
When NOT to Use
- A constructor or struct literal is already clear enough.
- The build steps add ceremony without capturing real workflow.
- Only field assignment changes and there is no reusable assembly process.