Overview
The Builder pattern constructs complex objects step by step with a readable, fluent API, then produces the final (often immutable) object with build(). It replaces 'telescoping constructors' with many parameters, where calls like new Pizza(12, true, false, true, null) are unreadable and error-prone.
Builders also centralize validation: build() checks that required fields are set and combinations are valid before the object exists. The resulting object can be immutable and always valid. Director classes can encode standard recipes (a Margherita preset) on top of a builder.
You tell the counter: bread, then cheese, then toppings, then sauce, and finally 'that's it'. The sandwich is only made when you finish, and the staff can refuse invalid combinations.
When to use it
- Objects with many optional parameters.
- Immutable objects that need multi-step configuration.
- Construction with validation across several fields.
Where it shows up in interviews
Recognize it when: many optional fields or combinations.
- Design an HTTP request builder
- Design a pizza or coffee order
- Design a report query builder
Where it is used in real software
The JDK uses builders for strings and HTTP requests.
Generates builder classes for Java data types.
Knex, jOOQ, and SQLAlchemy build SQL step by step.
Key terms
- Builder
- Mutable helper that collects settings.
- build()
- Validates and returns the finished product.
- Fluent interface
- Methods return the builder to allow chaining.
- Director
- Encodes common build sequences.
- Telescoping constructor
- Many overloaded constructors with growing parameter lists.
How it works, step by step
- 1Make the product immutable
Private constructor taking the builder.
- 2Create the builder with required fields
In its constructor or static factory.
- 3Add fluent setters for optional fields
Return this.
- 4Validate in build()
Required fields and rules.
- 5Optionally add presets
Director or static factory methods.
Constructor vs builder
HTTP request with method, URL, headers, body, timeout, retries
| Approach | Call site | Problem |
|---|---|---|
| Telescoping constructor | new Request('POST', url, h, body, 5000, 3) | Which number is which? |
| Setters on the object | r.setTimeout(5000) after creation | Object can be half-built or mutated later |
| Builder | Request.post(url).header(...).timeout(5s).build() | Readable, validated, immutable |
NOWApproach: Telescoping constructor | Call site: new Request('POST', url, h, body, 5000, 3) | Problem: Which number is which?
Builders make construction self-documenting and produce objects that are valid from birth.
Implementation
public final class Pizza { private final int sizeInches; private final String crust; private final List<String> toppings; private final boolean extraCheese; private Pizza(Builder b) { this.sizeInches = b.sizeInches; this.crust = b.crust; this.toppings = List.copyOf(b.toppings); this.extraCheese = b.extraCheese; } public static Builder builder(int sizeInches) { return new Builder(sizeInches); } public static final class Builder { private final int sizeInches; private String crust = "regular"; private final List<String> toppings = new ArrayList<>(); private boolean extraCheese; private Builder(int sizeInches) { this.sizeInches = sizeInches; } public Builder crust(String crust) { this.crust = crust; return this; } public Builder topping(String t) { toppings.add(t); return this; } public Builder extraCheese() { this.extraCheese = true; return this; } public Pizza build() { if (sizeInches < 8 || sizeInches > 18) throw new IllegalStateException("Size must be 8-18 inches"); if (toppings.size() > 6) throw new IllegalStateException("Max 6 toppings"); return new Pizza(this); } }} Pizza p = Pizza.builder(12).crust("thin").topping("basil").topping("olive").extraCheese().build();Complexity and performance
Plus validation.
Can be generated.
Trade-offs
Builders add code; for 2-3 fields, a constructor or named parameters are simpler.
Missing required fields are caught at build() time unless you use staged builders.
Variants and related techniques
Interfaces enforce required steps at compile time.
Kotlin, Python, and TypeScript object literals often remove the need for builders.
Common mistakes
- Returning a mutable product.
Fix: Make the product immutable and copy collections.
- No validation in build().
Fix: Validate cross-field rules before creating the object.
Interview questions
When would you use a builder over a constructor?
When an object has many optional parameters or needs cross-field validation, and you want readable construction of an immutable, always-valid object.
Builder vs Factory?
Factories decide which class to create, usually in one call. Builders assemble one complex object step by step with configuration.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Builder for a coffee order with add-ons | Easy | Fluent API. |
| SQL query builder with where, orderBy, limit | Medium | Validation. |