Patterns: creation · 3 MIN READ

Builder: assemble then validate

Represent staged construction without making invalid results usable.

Represent staged construction without making invalid results usable. Builder separates assembly steps from the finished value. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.

Creational pattern · A MAP OF THE IDEA

  1. Builder collects assembly steps
  2. Build validates the combination
  3. Product owns its retained values

Useful when

  • Assembly has repeated or ordered operations
  • A finished product must satisfy cross-field rules

Watch out

  • A config literal may be easier to read
  • Reused builders can accidentally alias product data

Implementation & operation cost: Medium

Fit depends on the task

Small tool or CLI3/5
Domain-rich application3/5
Independent distributed services2/5

Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.

01

The design decision

Builder separates assembly steps from the finished value. In Go a named configuration struct is often sufficient; a builder becomes useful when assembly has repeated steps or derived data. Build should validate the final combination rather than assume every sequence was legal.

02

A complete Go example

Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.

go
package main

import (
	"errors"
	"fmt"
	"strings"
)

type Request struct {
	Path string
	Tags []string
}
type Builder struct {
	path string
	tags []string
}

func (b *Builder) Path(p string) { b.path = p }
func (b *Builder) Tag(t string)  { b.tags = append(b.tags, t) }
func (b Builder) Build() (Request, error) {
	if !strings.HasPrefix(b.path, "/") {
		return Request{}, errors.New("absolute path required")
	}
	return Request{Path: b.path, Tags: append([]string(nil), b.tags...)}, nil
}

func main() {
	b := &Builder{}
	b.Path("/reports")
	b.Tag("daily")
	r, err := b.Build()
	b.Tag("later")
	fmt.Println(r.Path, r.Tags, err)
}

// Output:
// /reports [daily] <nil>
Read the code

Build validates the path and copies tags into the finished Request. Adding another builder tag does not mutate the already built request. This is a staged mutable builder, not a promise of thread safety.

03

Tradeoffs and the simpler alternative

A builder with one setter per field can obscure a clear struct literal. Use it for genuinely staged or repeated assembly and return validation errors. State whether a builder can be reused and whether products share backing storage.

TAKE THIS WITH YOU

Validates assembled state before exposing the product. A builder with one setter per field can obscure a clear struct literal. Use it for genuinely staged or repeated assembly and return validation errors. State whether a builder can be reused and whether products share backing storage.

Check your understanding

Which statement best describes Builder: assemble then validate?

Go to the source

Progress stays in this browser. No account needed.