Builder Pattern
Builder Pattern
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
Intent
The Builder pattern addresses the telescoping constructor problem — when a class has many optional parameters, every combination requires a constructor overload. Instead, a Builder accumulates parameters step-by-step, then constructs the final Product via a build() call. An optional Director encapsulates a fixed construction sequence for repeated use.
When NOT to Use
- Object has 3 or fewer parameters — a constructor is more explicit and compiler-checked
- All parameters are required — Builder adds indirection without benefit; use a constructor with all required params
- The product is simple and unlikely to evolve — Builder is overkill for stable simple objects
- Performance is critical at construction time — Builder adds allocation and method call overhead vs a direct constructor
When to Use
- Constructor would require 4+ parameters, especially with many optional ones (telescoping constructor smell)
- Same construction steps must produce different representations (e.g., HTML document vs Markdown document)
- Complex object must be built incrementally — some parts depend on previous parts
- You want to enforce invariants:
build()can validate that all required fields are set before returning the product
Suitability Threshold
When a constructor would require 4+ parameters, especially with optional ones, Builder is justified. Below that, use a constructor or named options object.
How It Works
Participants: Builder (interface with step methods), ConcreteBuilder (implements steps, holds product state), Director (optional — encapsulates a fixed step sequence), Product (the complex object).
The fluent interface (returning this from each step) is a modern variation that does not require a Director.
Class Diagram
classDiagram
class Builder {
<<interface>>
+buildStepA()*
+buildStepB()*
+buildStepC()*
+getResult()* Product
}
class ConcreteBuilder {
-product : Product
+buildStepA()
+buildStepB()
+buildStepC()
+getResult() Product
}
class Director {
-builder : Builder
+construct()
}
class Product {
+partA
+partB
+partC
}
Builder <|.. ConcreteBuilder
Director o-- Builder : uses
ConcreteBuilder ..> Product : builds
TypeScript Example
// Builder — TypeScript (fluent interface)
class QueryBuilder {
private table = "";
private conditions: string[] = [];
private columns = "*";
private limit?: number;
from(table: string): this { this.table = table; return this; }
select(cols: string): this { this.columns = cols; return this; }
where(cond: string): this { this.conditions.push(cond); return this; }
take(n: number): this { this.limit = n; return this; }
build(): string {
const where = this.conditions.length ? ` WHERE ${this.conditions.join(" AND ")}` : "";
const limit = this.limit != null ? ` LIMIT ${this.limit}` : "";
return `SELECT ${this.columns} FROM ${this.table}${where}${limit}`;
}
}
// Director is optional — useful when the same fixed sequence is reused
class ReportQueryDirector {
construct(builder: QueryBuilder): string {
return builder.from("orders").select("id, total").where("status = 'active'").take(100).build();
}
}
// Usage without Director
const query = new QueryBuilder().from("users").select("name, email").where("active = true").build();
// Usage with Director
const reportQuery = new ReportQueryDirector().construct(new QueryBuilder());Java Example
// Builder — Java (fluent interface)
public class QueryBuilder {
private String table = "";
private List<String> conditions = new ArrayList<>();
private String columns = "*";
private Integer limit;
public QueryBuilder from(String table) { this.table = table; return this; }
public QueryBuilder select(String cols) { this.columns = cols; return this; }
public QueryBuilder where(String cond) { this.conditions.add(cond); return this; }
public QueryBuilder take(int n) { this.limit = n; return this; }
public String build() {
String where = conditions.isEmpty() ? "" : " WHERE " + String.join(" AND ", conditions);
String lim = limit != null ? " LIMIT " + limit : "";
return "SELECT " + columns + " FROM " + table + where + lim;
}
}
// Usage
String query = new QueryBuilder().from("users").select("name, email").where("active = true").build();Related Concepts
| Concept | Relationship |
|---|---|
| Factory-Method-Pattern | Factory Method selects which class to instantiate; Builder constructs one complex object step-by-step |
| Abstract-Factory-Pattern | Abstract Factory creates a product family in one call; Builder assembles one product incrementally |
| Prototype-Pattern | Prototype clones existing instances; Builder constructs from scratch |
| Singleton-Pattern | Director is sometimes a Singleton (use DI-managed instance instead) |
Sources
- Gamma et al., Design Patterns, 1994, pp. 97–106 — authoritative definition and Director role
- Bloch, Effective Java 3rd ed., Item 2 — Java telescoping constructor problem and Builder solution
- refactoring.guru/design-patterns/builder — modern fluent builder framing