CMD Guide
HomeOO & Low-Level DesignCreational

Builder Pattern

The Builder pattern is a creational pattern that separates the construction of a complex object from its representation, so the same step-by-step process can produce different results. In day-to-day engineering its most common job is narrower and more practical than the textbook definition suggests: it lets you assemble an object that has many parameters — especially many optional ones — without a forest of overloaded constructors and without forcing callers to memorize positional argument order.

The problem it solves

Imagine a Pizza with a size, a crust, and a variable list of toppings. With plain constructors you either write one giant constructor and pass null for everything you don't care about, or you write a combinatorial pile of overloads. Both are error-prone: new Pizza("Large", "Thin", null) tells the reader nothing about which argument is which, and two String parameters in the wrong order compile happily and fail silently.

A builder turns that into a readable, self-documenting chain where every value is labeled by the method that sets it, and where the object is only materialized once, in a final build() call, after which it can be made immutable.

diagram
diagram

The fluent (modern) builder

This is the form you will write 95% of the time: a static nested Builder whose setters return this so calls chain, and a single build() that hands back an immutable product. There is no separate director — the caller drives the steps directly.

JDK baseline: the Java sample below uses List.of and List.copyOf, which require JDK 9 or later. On JDK 8, substitute Collections.unmodifiableList(new ArrayList<>(toppings)) and an empty new ArrayList<>() default.

// Java (JDK 9+)
public final class Pizza {
    private final String size;
    private final String crust;
    private final List<String> toppings;   // immutable

    private Pizza(Builder b) {
        this.size = b.size;
        this.crust = b.crust;
        this.toppings = List.copyOf(b.toppings); // defensive, unmodifiable copy
    }

    @Override public String toString() {
        return "Pizza{size=" + size + ", crust=" + crust + ", toppings=" + toppings + "}";
    }

    public static final class Builder {
        // sensible defaults for optional fields
        private String size  = "Medium";
        private String crust = "Hand-tossed";
        private List<String> toppings = new ArrayList<>();

        public Builder size(String size)   { this.size = size;   return this; }
        public Builder crust(String crust) { this.crust = crust; return this; }
        public Builder addTopping(String t) { this.toppings.add(t); return this; }

        public Pizza build() { return new Pizza(this); }
    }
}

// Usage
Pizza hawaiian = new Pizza.Builder()
        .size("Large")
        .crust("Thin")
        .addTopping("Ham")
        .addTopping("Pineapple")
        .build();
// Pizza{size=Large, crust=Thin, toppings=[Ham, Pineapple]}

Pizza plain = new Pizza.Builder().build();
// Pizza{size=Medium, crust=Hand-tossed, toppings=[]}

The same shape in Go uses a pointer receiver that returns the builder for chaining. Go has no constructors, so the builder is the idiomatic way to express defaults plus optional fields:

// Go
type Pizza struct {
    Size     string
    Crust    string
    Toppings []string
}

func (p Pizza) String() string {
    return fmt.Sprintf("Pizza{size=%s, crust=%s, toppings=[%s]}",
        p.Size, p.Crust, strings.Join(p.Toppings, ", "))
}

type PizzaBuilder struct {
    size, crust string
    toppings    []string
}

func NewPizzaBuilder() *PizzaBuilder {
    return &PizzaBuilder{size: "Medium", crust: "Hand-tossed"}
}

func (b *PizzaBuilder) Size(s string) *PizzaBuilder       { b.size = s; return b }
func (b *PizzaBuilder) Crust(c string) *PizzaBuilder      { b.crust = c; return b }
func (b *PizzaBuilder) AddTopping(t string) *PizzaBuilder { b.toppings = append(b.toppings, t); return b }

func (b *PizzaBuilder) Build() Pizza {
    cp := make([]string, len(b.toppings))
    copy(cp, b.toppings) // defensive copy so the slice can't be mutated later
    return Pizza{Size: b.size, Crust: b.crust, Toppings: cp}
}

func main() {
    hawaiian := NewPizzaBuilder().
        Size("Large").Crust("Thin").
        AddTopping("Ham").AddTopping("Pineapple").
        Build()
    fmt.Println(hawaiian)
    // Pizza{size=Large, crust=Thin, toppings=[Ham, Pineapple]}

    plain := NewPizzaBuilder().Build()
    fmt.Println(plain)
    // Pizza{size=Medium, crust=Hand-tossed, toppings=[]}
}

(Both programs were compiled and produce exactly the output shown in the trailing comments.)

The classic GoF form: a Director driving a Builder interface

The original Gang of Four formulation adds two pieces the fluent form drops: a Builder interface with one concrete builder per representation, and a Director that knows the construction recipe — the sequence of build steps — but not the concrete product. This separation pays off when one fixed assembly sequence must yield several different output formats.

The canonical example is a document generator: the same logical document — a title, a paragraph, a bullet list — rendered either as HTML or as Markdown. The Director encodes the recipe once; swapping the builder swaps the output format.

// Java (JDK 9+) — GoF Builder with a Director

// Builder interface: the construction steps, format-agnostic
interface DocBuilder {
    void addTitle(String text);
    void addParagraph(String text);
    void addBullets(List<String> items);
    String build();   // returns the finished representation
}

// Concrete builder #1 — HTML
class HtmlDocBuilder implements DocBuilder {
    private final StringBuilder sb = new StringBuilder();
    public void addTitle(String t)      { sb.append("<h1>").append(t).append("</h1>\n"); }
    public void addParagraph(String t)  { sb.append("<p>").append(t).append("</p>\n"); }
    public void addBullets(List<String> items) {
        sb.append("<ul>\n");
        for (String i : items) sb.append("  <li>").append(i).append("</li>\n");
        sb.append("</ul>\n");
    }
    public String build() { return sb.toString(); }
}

// Concrete builder #2 — Markdown
class MarkdownBuilder implements DocBuilder {
    private final StringBuilder sb = new StringBuilder();
    public void addTitle(String t)      { sb.append("# ").append(t).append("\n\n"); }
    public void addParagraph(String t)  { sb.append(t).append("\n\n"); }
    public void addBullets(List<String> items) {
        for (String i : items) sb.append("- ").append(i).append("\n");
        sb.append("\n");
    }
    public String build() { return sb.toString(); }
}

// Director: owns the recipe, not the format
class ReportDirector {
    private final DocBuilder builder;
    ReportDirector(DocBuilder builder) { this.builder = builder; }

    public String construct() {
        builder.addTitle("Quarterly Report");
        builder.addParagraph("Revenue grew this quarter.");
        builder.addBullets(List.of("North up 12%", "EMEA flat", "APAC up 8%"));
        return builder.build();
    }
}

// Client: pick a builder, hand it to the director
String html = new ReportDirector(new HtmlDocBuilder()).construct();
String md   = new ReportDirector(new MarkdownBuilder()).construct();
// html  -> <h1>Quarterly Report</h1> ... <ul><li>North up 12%</li> ...
// md    -> # Quarterly Report\n\nRevenue grew... \n- North up 12% ...

Note what each role owns. The Director (ReportDirector) owns the order and choice of steps — "title, then paragraph, then bullets." The concrete builders own how each step renders. The client owns only the decision of which representation it wants. Add a PlainTextBuilder tomorrow and neither the Director nor the existing builders change.

Mapping this back to the four GoF roles: DocBuilder is the Builder, HtmlDocBuilder/MarkdownBuilder are ConcreteBuilders, ReportDirector is the Director, and the returned String is the Product. The classic Pizza-shop example phrases the same roles as a Waiter (Director) directing a HawaiianPizzaBuilder (ConcreteBuilder) to assemble a Pizza (Product).

diagram
diagram

When NOT to reach for a builder

A builder is not free. Every builder is an extra class (or nested class), an extra allocation per object you construct (the builder instance itself, separate from the product), and a layer of boilerplate that must be kept in sync with the product's fields. Use it only when that cost buys you something. Skip it when:

Trade-offs at a glance

ConcernPlain constructorFluent builderGoF Director + Builder
Best whenFew, all-required fieldsMany fields, several optionalOne recipe, many output formats
Readability of call sitePoor with many argsHigh (named steps)High; recipe centralized
Extra classes / allocationNoneOne builder per buildBuilder(s) + a Director
Enforces required fieldsAt compile timeOnly via runtime checksOnly via runtime checks

Key takeaways

References

Adapted and corrected from the original course page's Pizza-shop example, whose Waiter (Director) and HawaiianPizzaBuilder (ConcreteBuilder) map onto the GoF roles described above; the document-generator example is provided here to show the complete Director-driven variant end to end.

Interview drills & follow-ups

🤖 Don't fully get this? Learn it with Claude

Stuck on Builder Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Builder Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Builder Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Builder Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Builder Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Builder Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes