Skip to main content

Patternbeginner

Builder

Overview

Builder separates the construction of a complex object from its representation, so that the same construction process can produce different representations.

In practice, the name covers two distinct patterns, and confusing them is the source of most of the misunderstandings.

Problem

The original GoF pattern solves a specific case: an assembly process in steps, with a director that knows the order and a builder that knows the representation. Parsing a document and producing HTML or plain text with the same traversal is the canonical example.

The predominant use today is another: constructing objects with many optional parameters. An object with twelve fields, three of them mandatory, does not fit in a readable constructor — and a sequence of setters allows invalid states between calls.

Both problems are real. They are different.

Core Concepts

GoF Builder — one process, several representations

The director's arrow ends at the interface: it never reaches a concrete builder. Both builders share that interface, while the products they return need not share a type.

The director runs the steps in order. Each builder decides what to do with them. Swapping the builder changes the output without changing the traversal.

Parameter builder — readable, safe construction

Order.of(customerId, items) ← mandatory up front
.withDiscount(discount)
.withExpressDelivery()
.withNote(text)
.build() ← validates and constructs at once

Two properties that matter: the mandatory ones are required, and the object only exists after build() — there is never a partially assembled instance.

That solves the telescoping constructor (a cascade of overloaded constructors) and the mutable object with setters. See encapsulation.

What decides between the two

If there is one traversal with several possible outputs, it is the GoF one. If there is one object with many parameters, it is the modern one.

Applying the GoF one to the second problem produces a director that does nothing.

When to Use

Parameter builder:

  • More than four or five parameters, several optional.
  • The object should be immutable and valid from creation.
  • Combinations of parameters with rules between them.

GoF Builder:

  • The same construction traversal has to produce different representations.
  • The order of the steps is known and stable, and the representation varies.

When Not to Use

When there are few parameters. Two or three mandatory ones fit in a constructor. The builder adds code and indirection.

When the language has named parameters with default values. In Python, Kotlin, C# and others, the problem the parameter builder solves is already solved by the language, and the pattern becomes ceremony.

When the object is genuinely mutable. If it will change after creation, the guarantee of validity at construction does not buy what it promises.

When the director has nothing to direct. Applying the GoF Builder with no alternative representations produces a class with no function.

For transport objects. A DTO with ten fields that merely carries data does not need a builder — it needs a transparent record.

Alternatives

  • Named parameters with defaults — if the language offers them, it is superior.
  • A parameter object — group the related ones into their own type.
  • Named factory methods — when the valid combinations are few and known: Order.expressFor(customer).
  • A simple constructor — when the parameters are few.

Trade-offs

BuilderDirect constructor
Readable, self-explanatory callObscure positional order
Object valid and immutable at the endIntermediate states possible
Optionals without combinatorial overloadsCascade of constructors
One extra class per typeNothing extra
Risk of forgetting build()No such possibility

Failure Modes

Exposed mutable builder. The builder is passed around and altered in several places before building.

Validation only at the end, too late. The error appears far from the call that caused it. Validating each step where possible helps.

Empty director. GoF Builder with no alternative representations.

Builder generated for everything. Tools that generate a builder for every class produce code nobody needs.

Common Mistakes

Calling the parameter builder "the GoF Builder pattern". They are different problems.

Using it in a language with named parameters. Ceremony over a language feature.

Letting partial state escape. The object should only exist complete.

Applying it to DTOs. With no invariant to protect, there is nothing to guarantee.

Real-World Example

A ReportQuery class accumulated fifteen fields over two years: period, filters, groupings, ordering, format, limits.

The constructor had seven overloads. Three of them differed only in the order of parameters of the same type, and a production defect came from exactly that: someone swapped startDate with endDate in a call, and the compiler did not complain because both were the same type.

The builder solved it in two ways. The names made the swap visible on reading. And build() came to validate that the start precedes the end — validation that previously existed nowhere because there was no single construction point.

The honest detail: had the system been in a language with named parameters, the first half of the benefit would have come for free, and only the central validation would remain — which would not justify the extra class.

Where it appears in practice

StringBuilder. It is neither the GoF pattern nor the parameter builder — it is efficient accumulation with chaining. The shared name confuses, and it is worth knowing these are three distinct things with the same word.

HTTP clients. HttpRequest.newBuilder().uri(...).header(...).build() is the parameter builder: many optionals, an immutable object at the end.

Query builders. Fluent interfaces that assemble SQL in steps are the GoF Builder when the same traversal produces different dialects.

Test libraries. Constructing complex domain objects in test scenarios is one of the highest-return uses, because the test becomes readable: what is particular about the scenario is explicit, and the rest takes defaults.

That last use often justifies the builder on its own, even when production code would not need it — the test's readability is what makes someone consult it as documentation.

Practical Exercise

Find the class in your system with the most parameters in its constructor. Count how many are of the same type and adjacent — each such pair is a silent swap waiting to happen.

Then check whether the language you use has named parameters. If it does, compare the builder solution with the language's.

Interview Questions

  • What are the two problems the name "Builder" covers?
  • When is the parameter builder unnecessary?
  • What guarantee does the builder give that a sequence of setters does not?

Further Exploration

  • Gamma, Erich et al. Design Patterns. Addison-Wesley, 1994.
  • Bloch, Joshua. Effective Java. 3rd ed., 2018 — the parameter builder.
Finished reading this document?Your progress is saved in this browser only.