skip to content

The Gang of Four describe Builder as 'separate the construction of a complex object from its representation so that the same construction process can create different representations', with a Director driving an abstract Builder interface. How does that differ from the popular fluent nested-class builder, and when does the original form still matter?

level: seniorimportance: should knowfreq 40%

answer

  1. Director = construction process, ConcreteBuilder = representation
  2. same walk → HTML / text / LaTeX
  3. getResult() on the concrete builder; products may be unrelated
  4. fluent builder: no interface, caller is the Director
  5. fluent = readable optional params, GoF = varying output format

basics

~20 s

The fluent builder mainly names optional parameters for one class. The original Builder has an interface with several implementations plus a Director that calls the steps in a fixed order, so one construction process can produce different outputs — for example the same document walk emitting HTML or plain text.

solid answer

~50 s

Two patterns share a name. The **GoF form** has four roles: an abstract *Builder* interface declaring the construction steps, several *ConcreteBuilders* that each realise those steps into a different *Product* representation, and a *Director* that knows the sequence of steps but not the representation. Swapping the concrete builder changes the output while the algorithm stays fixed — a document parser emitting HTML, LaTeX or plain text; a query AST rendered as SQL or as a Mongo filter; SAX-style parsers handing events to pluggable handlers. It is polymorphic and the abstraction lives in the builder interface. The **fluent/Effective-Java form** has one builder, usually a static nested class of a single product, no interface and no Director; the caller *is* the director, and the point is readable named optional parameters plus an immutable, validated result. Most day-to-day "builders" are the second. The first still earns its keep whenever the traversal or assembly algorithm is shared but the output format varies, and it composes naturally with Visitor and with streaming parsers.

code

pseudocode · 13 lines
pseudocode
// GoF form: one process, swappable representation
interface DocBuilder { heading(t, lvl); paragraph(t); }
class HtmlBuilder      implements DocBuilder { /* emits <h1>… */ getResult(): String }
class PlainTextBuilder implements DocBuilder { /* emits UPPERCASE… */ getResult(): String }

function render(doc, builder) {            // the Director
  for (node in doc.nodes)
    node.isHeading ? builder.heading(node.text, node.level)
                   : builder.paragraph(node.text)
}

// Fluent form: one representation, caller supplies the order
var r = Report.builder().title("Q3").author("kim").build()

go deeper

for a junior

Know that the fluent nested-class builder exists and what it is for; recognising that a Director/interface variant exists is a bonus.

for a middle

Name all four GoF roles and give one concrete example of the same process producing different representations.

for a senior

Contrast intents and costs, explain why getResult() lives on the concrete builder, and say when a Director is warranted versus being ceremony over a static factory method.

for a principal

Position it against Abstract Factory, Visitor and Composite; judge when the polymorphic form is worth the indirection (multiple output formats, pluggable sinks, streaming parsers) versus a generated fluent builder or plain named parameters.

### Two things wearing one name Interviewers use "Builder" for both of these, and candidates who only know one are easy to catch out. #### 1. GoF Builder (1994) — polymorphic, Director-driven Roles: - **Builder (abstract)** — declares the construction steps: `startDocument()`, `addHeading(text, level)`, `addParagraph(text)`, `endDocument()`, `getResult()`. - **ConcreteBuilder** — one per representation: `HtmlBuilder`, `PlainTextBuilder`, `MarkdownBuilder`. Each keeps its own partial result and knows how to render each step. - **Director** — holds the *construction algorithm*: it walks the input and calls the steps in the right order. It is written once and knows nothing about the output format. - **Product** — what a concrete builder finally returns. Crucially, **products need not share a base type**; `HtmlBuilder.getResult()` may return a string while `DomBuilder.getResult()` returns a tree. That is why `getResult()` sits on the concrete builder, not on the abstract interface, in the original diagram. ``` interface DocBuilder { void heading(String text, int level); void paragraph(String text); } class Director { // the construction *process* void render(Doc doc, DocBuilder b) { for (Node n : doc.nodes()) if (n.isHeading()) b.heading(n.text(), n.level()); else b.paragraph(n.text()); } } new Director().render(doc, new HtmlBuilder()); // -> HTML string new Director().render(doc, new PlainTextBuilder()); // -> plain text ``` The intent sentence maps directly: *construction* (the Director's loop) is separated from *representation* (which concrete builder is passed), so *the same construction process creates different representations*. Real-world shapes of this form: SAX/StAX-style parsing where a handler builds whatever it likes from the same event stream; compiler/AST emitters where one traversal targets several backends; `StringBuilder`-like sinks plugged into a shared formatter; test-data assembly where one scenario script drives builders that produce either in-memory objects or SQL fixtures. #### 2. Fluent builder — one product, named optional parameters Popularised by *Effective Java* Item 2 and by code generators (Lombok's `@Builder`, Kotlin/Rust builder idioms, protobuf builders): - One `Builder`, usually a static nested class of exactly one product. - No interface, no polymorphism, no Director — **the caller writes the construction sequence inline**, so the caller plays the Director role. - Steps return `this` for chaining; `build()` validates and constructs the immutable product. - Intent: readability of many/optional parameters, immutability, single-point validation. Not "different representations" — there is exactly one representation. ### The comparison an interviewer wants | | GoF Builder | Fluent builder | |---|---|---| | Varies at runtime | the representation (which ConcreteBuilder) | nothing — one product type | | Who orders the steps | Director (reusable algorithm) | the caller, inline | | Abstraction | builder interface with several impls | none needed | | Typical output | possibly unrelated product types | one immutable product | | Main benefit | reuse of a complex assembly/traversal across formats | readable optional params, immutability, validation | | Main cost | more types, indirection | duplicated field list | ### Where the Director really pays off Add a Director to a fluent builder only when the *sequence itself* is worth naming and reusing — e.g. `PizzaDirector.margherita(builder)`, `ReportDirector.monthlySummary(builder)`. That gives you named recipes: the recipe is written once, and passing a different builder yields a different medium (a PDF vs an HTML preview vs a JSON payload). Without varying representations, a Director is usually just a static factory method with extra ceremony — and many modern codebases legitimately drop it. ### Neighbouring patterns, so you do not confuse them - **Abstract Factory** also varies families of products polymorphically, but it creates each product in *one* call; Builder assembles *one* product over *many* calls and can therefore carry state and step order. - **Visitor** also separates a traversal from what happens at each node; the combination "Visitor walks, Builder accumulates" is extremely common and is essentially the GoF Builder with the Director's loop factored into a visitor. - **Composite** is often the product a GoF Builder assembles (a tree built step by step). - **Fluent interface** is an idiom (method chaining), not a pattern — jQuery-style chains and stream pipelines are fluent without being builders.

  • Can you drop the Director and still call it the GoF Builder?
    You keep most of the value — the polymorphic builder interface still lets one algorithm target several representations — but the algorithm now lives in the caller, so it is not reusable and cannot be swapped independently. The Director exists precisely to name and reuse the construction sequence; drop it when there is only one caller.
  • Why does getResult() sit on the concrete builder rather than the Builder interface in the original design?
    Because the products may have no common supertype: an HTML builder returns a string, a DOM builder returns a tree. Forcing a shared return type would either be a meaningless base type or push you toward Abstract Factory. In modern code you can often generify the interface (Builder<T>) if the products really do share a shape.
  • How is this different from Abstract Factory?
    Abstract Factory creates each product in a single call and varies whole *families* of related products. Builder assembles one product across many stateful calls, so it can enforce step order and accumulate partial results — and only Builder has the notion of a construction process separate from the representation.

A recipe read aloud (Director) to different kitchens (concrete builders): the same sequence of instructions yields a cake in one kitchen and a gluten-free cake in another. The fluent builder is a single kitchen where you just tick ingredients on a form.

saying these in an interview costs you the question

  • Reciting only the fluent nested-class form and calling that 'the' Builder pattern
  • Saying Builder always needs a Director — most production builders have none
  • Assuming all concrete builders must return the same product type
  • Confusing method chaining (a fluent interface) with the Builder pattern
  • Describing Abstract Factory's family selection as Builder's 'different representations'

context