Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Builder
LLD

Builder

Understand how to construct complex objects step by step while keeping construction separate from representation.

Builder

The Problem It Solves

// which of these 8 arguments is the max-retry? the timeout? nobody knows:
new HttpClient("https://api", 5000, true, false, 3, "UTF-8", 60, null);

Telescoping constructors fail four ways: argument meaning is invisible at call sites; optional parameters force overload pyramids (2ⁿ combinations); identical adjacent types (int, int) invite transposition bugs that compile fine; and adding a parameter breaks every call. Builder fixes all four by replacing positional construction with named steps.

Mechanics

HttpClient client = HttpClient.builder()
        .url("https://api.example.com")
        .timeout(Duration.ofSeconds(5))
        .maxRetries(3)
        .gzip(true)
        .build();

Every step names its intent; order is free; omitted optionals fall back to defaults.

public final class HttpClient {
    private final String url;
    private final Duration timeout;
    private final int maxRetries;

    private HttpClient(Builder b) {           // private ctor — only builder builds
        this.url = b.url;
        this.timeout = b.timeout;
        this.maxRe tries = b.maxRetries;
    }

    public static Builder builder() { return new Builder(); }

    public static final class Builder {
        private String url;                          // required — no default
        private Duration timeout = Duration.ofSeconds(30);
        private int maxRetries = 0;

        public Builder url(String url)   { this.url = url; return this; }      // return this = fluent chain
        public Builder timeout(Duration t){ this.timeout = t; return this; }
        public Builder maxRetries(int n) { this.maxRetries = n; return this; }

        public HttpClient build() {
            if (url == null || url.isBlank())
                throw new IllegalStateException("url required");  // validation HERE,
            return new HttpClient(this);                           // once, centrally
        }
    }
}

Three load-bearing decisions inside: return this enables chaining; the outer constructor is private so no path bypasses validation; validation lives in build() where all fields are final-assembled — invariants checked exactly once on complete state (never on half-built objects).

What You Get

PropertyWhy it follows
Immutable productsAll fields set before construction completes
Readable call sitesEach value labeled by its setter name
Centralized defaultsIn one class, not scattered overloads
One-time validationIn build() — see maintaining-invariants

Real-World Sightings

  • JDK: Stream.Builder, Locale.Builder, HttpRequest.newBuilder().
  • Lombok @Builder generates this shape — acceptable boilerplate elimination for data-centric classes.
  • Kafka/OkHttp/gRPC clients are all fluent builders; configuration surfaces industry-wide converged here.

Trade-offs & Limits

  • Boilerplate per class (~1.5× field count lines) — worth it past ~4 fields or with any validation.
  • Not a fit for tiny stable-value classes (Money.of(...)) — factory methods win.
  • Builder instances are single-use after build() when product holds derived state; document or reset explicitly.

Interview Framing

  • Asked to model config-style objects, writing the builder unprompted scores; stating why validation goes in build() (complete-state invariant checks) scores higher.

The Original Form

GoF’s builder separates what gets built (a fixed sequence of assembly steps) from how each step is performed (the concrete builder). One construction recipe — the director — run against different builders yields entirely different products: same steps, PDF report vs HTML report.

                    ┌────────────────────┐
   client ────────► │ Director           │
                    │ construct():       │
                    │  b.addHeader()     │   fixed recipe — knows the ORDER,
                    │  b.addBody(items)  │   knows nothing about output format
                    │  b.addFooter()     │
                    └─────────┬──────────┘
                              │ drives steps on ▼
                    ┌────────────────────┐
                    │ «interface» Builder│
                    │ +addHeader()       │
                    │ +addBody(...)      │
                    │ +addFooter()       │
                    │ +getResult()       │
                    └─────△───────△──────┘
                          │       │
                 ┌────────┴─┐   ┌─┴────────┐
                 │PdfBuilder│   │HtmlBuilder│
                 (each step emits its own representation)

The dashed line between director and product is deliberate in the GoF diagram: directors don’t know what they’re building — only the step sequence.

Mechanics

interface ReportBuilder {
    void addHeader(String title);
    void addBody(List<Line> lines);
    void addFooter(String page);
    Report result();
}

class Director {
    Report construct(ReportBuilder b, String title, List<Line> lines) {
        b.addHeader(title);          // ordering policy lives HERE,
        for (var l : lines)          // once, shared by every format
            b.addBody(List.of(l));
        b.addFooter("confidential");
        return b.result();
    }
}

new Director().construct(new PdfBuilder(), ...);PDF report
new Director().construct(new HtmlBuilder(), ...);HTML report

When the Director Earns Its Keep

SituationVerdict
Multiple representations from one assembly orderDirector valuable
Callers assemble ad hoc via fluent chainSkip director — plain fluent builder is simpler
Ordering rules are business logic (“footer always last”)Director centralizes them

Honest production guidance: most modern usage is fluent-builder only; the director returns when a shared multi-step recipe genuinely exists.

vs Fluent Builder

GoF builder + directorFluent builder
Driven byDirector’s fixed recipeCaller’s chained calls
PurposeSame process → different representationsReadable/validated construction of one type
ComplexityHigher (three roles)Lower

Real-World Sightings

  • Document generation frameworks (Apache POI workbooks): one sheet-filling routine, XLSX/PDF/CSV writers as builders.
  • SQL query builders where a dialect-agnostic plan renders to MySQL or Postgres syntax.
  • Text parsers building different ASTs from identical traversal order.

Interview Framing

  • Explaining that the director encodes ordering invariants (header-before-body) is the understanding marker — it answers “why not let callers chain freely?”

My Private Notes

Notes are auto-saved locally to this device.