Builder Design Pattern
I once had a constructor that took 11 parameters. Positional arguments. No defaults. Every call looked like new Report(true, false, null, "pdf", 12, null,...
23 Mar 2024

I once had a constructor that took 11 parameters. Positional arguments. No defaults. Every call looked like new Report(true, false, null, "pdf", 12, null, null, true, "A4", false, "en"). Nobody could read it. Nobody wanted to touch it.
The Builder pattern replaces monster constructors with a step-by-step, readable construction process. You chain method calls, each one setting a single property, then call build() to get the final object.
Think of it like ordering a burrito. You don't hand the cashier a 10-field form. You walk down the line: rice, beans, protein, salsa, done.
class Pizza {
constructor() {
this.crust = "";
this.sauce = "";
this.toppings = [];
}
describe() {
console.log(
`Pizza with ${this.crust} crust, ${this.sauce} sauce, and toppings: ${this.toppings.join(", ")}.`
);
}
}
class PizzaBuilder {
constructor() {
this.pizza = new Pizza();
}
addCrust(crust) {
this.pizza.crust = crust;
return this;
}
addSauce(sauce) {
this.pizza.sauce = sauce;
return this;
}
addTopping(topping) {
this.pizza.toppings.push(topping);
return this;
}
build() {
return this.pizza;
}
}
const pizza = new PizzaBuilder()
.addCrust("thin")
.addSauce("tomato")
.addTopping("cheese")
.addTopping("mushrooms")
.build();
pizza.describe();
// Pizza with thin crust, tomato sauce, and toppings: cheese, mushrooms.
Each method on PizzaBuilder returns this, enabling the fluent chain. build() returns the finished Pizza. The client never sets properties directly on the product — the builder controls the construction process.
You can create multiple builders for different configurations. A MargheritaBuilder could preset sauce and cheese. A VeganBuilder could restrict topping choices.
The benefit: Readable construction code. Optional parameters are obvious. You can validate the object in build() before returning it. The product stays immutable after construction.
The cost: More classes. For simple objects with two or three fields, a plain constructor or an options object { crust: "thin", sauce: "tomato" } is simpler. Builder earns its keep when construction logic is complex or when the same process needs to produce different representations.
I use builders for test data factories, complex configuration objects, and query construction. Anywhere a constructor has more than four or five parameters, I reach for a builder.