Builder
Builder pulls the step-by-step assembly of a complex object out into a separate object, so the same sequence of steps can produce different finished configurations of that object.
Problem
A `House` object needs a wall count, a door count, a window count, and optional extras like a roof or a garage - most of which are optional and many of which depend on each other. A constructor covering every combination either explodes into `new House(walls, doors, windows, hasRoof, hasGarage, ...)` with half the arguments set to defaults nobody remembers the order of, or it multiplies into a subclass for every popular layout: `SimpleFourWallHouse`, `FamilyHouseWithGarage`, and so on, which stops scaling the moment a client wants something slightly different.
Both escape routes trade one problem for another: an unreadable, error-prone giant constructor, or a subclass explosion that still can't cover every custom layout.
Solution
Move the assembly logic into a separate builder object that exposes one method per configurable part - `buildWalls()`, `buildDoors()`, `buildWindows()`, `buildRoof()`, `buildGarage()` - each returning the builder itself so calls can be chained. The caller invokes only the steps relevant to a particular house and skips the rest; there's no giant parameter list to fill in blanks for.
Different builder classes implementing the same step interface can assemble entirely different representations from the same sequence of calls - a `HouseBuilder` and a `HouseBlueprintBuilder` could both consume the same steps but produce a livable house object versus a printable blueprint. When a handful of standard layouts are ordered often, a separate Director object can encode those recipes as fixed sequences of builder calls (`buildSimpleHouse()`, `buildFamilyHouse()`), while ad-hoc houses still go straight through the builder without a director at all.
When to Use
- Reach for it when a constructor would need many optional parameters and most calls would only set a handful of them.
- Reach for it when the same underlying construction process needs to yield visibly different end results depending on which builder or steps are used.
- Reach for it when assembling an object involves ordering constraints or nested sub-objects that are easier to express as discrete steps than as one call.
Real-World Examples
- **`fetch`/`Request` construction via chained config objects and libraries like `axios`** - request options accumulate through method calls or merged config before the actual network call fires.
- **`StringBuilder` in Java and `StringBuilder`/template literals in .NET** - long strings are assembled through repeated `append()` calls instead of costly repeated concatenation.
- **Query builders in ORMs like Knex.js and TypeORM** - `.select().where().orderBy().limit()` chains assemble a SQL query step by step before it's compiled and executed.
Structure
A shared step interface lists every assembly operation a product might need. Each concrete builder implements those steps to accumulate state on its own internal product and exposes a way to retrieve the finished result. An optional Director holds fixed sequences of step calls for common configurations, while ad-hoc assembly can call the builder's steps directly.
Participants
Interface listing every step needed to assemble a product, independent of how any particular builder implements them.
In the diagram: Builder.
Implements each step by mutating its own in-progress product instance, and exposes a getResult() method to hand over the finished object.
In the diagram: HouseBuilder.
Encodes fixed recipes as ordered sequences of builder calls, so common configurations can be built with one method call instead of repeating steps everywhere.
In the diagram: Director.
The object under construction. Different builders may assemble unrelated product types through the same step interface - nothing forces them to share a hierarchy.
In the diagram: Product (House), assembled from Walls, Doors, Windows.
Creates a concrete builder (and an optional Director), invokes only the steps a particular configuration needs, then retrieves the finished product.
In the diagram: Client.
class House {
constructor() {
this.walls = 0;
this.doors = 0;
this.windows = 0;
this.hasRoof = false;
this.hasGarage = false;
}
toString() {
return `House: ${this.walls} walls, ${this.doors} doors, ` +
`${this.windows} windows, roof: ${this.hasRoof}, garage: ${this.hasGarage}`;
}
}
class HouseBuilder {
constructor() {
this.house = new House();
}
buildWalls(count) { this.house.walls = count; return this; }
buildDoors(count) { this.house.doors = count; return this; }
buildWindows(count) { this.house.windows = count; return this; }
buildRoof() { this.house.hasRoof = true; return this; }
buildGarage() { this.house.hasGarage = true; return this; }
getResult() {
const result = this.house;
this.house = new House();
return result;
}
}
class Director {
setBuilder(builder) { this.builder = builder; }
buildSimpleHouse() {
this.builder.buildWalls(4).buildDoors(1).buildWindows(2).buildRoof();
}
buildFamilyHouse() {
this.builder.buildWalls(6).buildDoors(3).buildWindows(8).buildRoof().buildGarage();
}
}
const builder = new HouseBuilder();
const director = new Director();
director.setBuilder(builder);
director.buildSimpleHouse();
console.log(builder.getResult().toString());
director.buildFamilyHouse();
console.log(builder.getResult().toString());
const custom = new HouseBuilder()
.buildWalls(4)
.buildDoors(2)
.buildWindows(4)
.getResult();
console.log(custom.toString());
Step 1 of 7
House is a plain product with no construction logic
The class just holds fields (`walls`, `doors`, `windows`, `hasRoof`, `hasGarage`) and a `toString()` - it knows nothing about how it gets assembled step by step.
Step 2 of 7
HouseBuilder starts with a fresh, empty House
The builder's constructor immediately creates a `new House()` and stores it as `this.house`, ready to be filled in by later calls.
Step 3 of 7
Each build step mutates one part and returns `this`
`buildWalls`, `buildDoors`, `buildWindows`, `buildRoof`, and `buildGarage` each set one field on `this.house`, then return `this` - that's what lets the calls be chained like `builder.buildWalls(4).buildDoors(1)`.
Step 4 of 7
getResult() hands over the house and resets for the next one
It saves a reference to the current `this.house`, replaces `this.house` with a brand-new empty one, then returns the saved reference - so the same builder can be reused to build another house from scratch.
Step 5 of 7
Director encodes two fixed build recipes
`buildSimpleHouse()` and `buildFamilyHouse()` each call a specific sequence of builder methods - the director knows *what steps in what order*, while the builder knows *how each step works*.
Step 6 of 7
The same builder produces two different houses via the director
`director.buildSimpleHouse()` then `builder.getResult()` yields a small house; `director.buildFamilyHouse()` then `getResult()` yields a bigger one - both built by the same `builder` instance, one recipe at a time.
Step 7 of 7
The builder can also be driven directly, without a director
This chain calls `buildWalls`, `buildDoors`, `buildWindows`, and `getResult()` straight on a new `HouseBuilder`, skipping `Director` entirely for a one-off custom house.
interface HouseBuilderInterface {
buildWalls(count: number): this;
buildDoors(count: number): this;
buildWindows(count: number): this;
buildRoof(): this;
buildGarage(): this;
}
class House {
walls: number = 0;
doors: number = 0;
windows: number = 0;
hasRoof: boolean = false;
hasGarage: boolean = false;
toString(): string {
return `House: ${this.walls} walls, ${this.doors} doors, ` +
`${this.windows} windows, roof: ${this.hasRoof}, garage: ${this.hasGarage}`;
}
}
class HouseBuilder implements HouseBuilderInterface {
private house: House = new House();
buildWalls(count: number): this { this.house.walls = count; return this; }
buildDoors(count: number): this { this.house.doors = count; return this; }
buildWindows(count: number): this { this.house.windows = count; return this; }
buildRoof(): this { this.house.hasRoof = true; return this; }
buildGarage(): this { this.house.hasGarage = true; return this; }
getResult(): House {
const result = this.house;
this.house = new House();
return result;
}
}
class Director {
private builder!: HouseBuilder;
setBuilder(builder: HouseBuilder): void {
this.builder = builder;
}
buildSimpleHouse(): void {
this.builder.buildWalls(4).buildDoors(1).buildWindows(2).buildRoof();
}
buildFamilyHouse(): void {
this.builder.buildWalls(6).buildDoors(3).buildWindows(8).buildRoof().buildGarage();
}
}
const builder = new HouseBuilder();
const director = new Director();
director.setBuilder(builder);
director.buildSimpleHouse();
console.log(builder.getResult().toString());
director.buildFamilyHouse();
console.log(builder.getResult().toString());
class House {
int walls, doors, windows;
boolean hasRoof, hasGarage;
public String toString() {
return String.format(
"House: %d walls, %d doors, %d windows, roof: %b, garage: %b",
walls, doors, windows, hasRoof, hasGarage);
}
}
class HouseBuilder {
private House house = new House();
public HouseBuilder buildWalls(int n) { house.walls = n; return this; }
public HouseBuilder buildDoors(int n) { house.doors = n; return this; }
public HouseBuilder buildWindows(int n) { house.windows = n; return this; }
public HouseBuilder buildRoof() { house.hasRoof = true; return this; }
public HouseBuilder buildGarage() { house.hasGarage = true; return this; }
public House getResult() { House r = house; house = new House(); return r; }
}
class Director {
private HouseBuilder builder;
public void setBuilder(HouseBuilder b) { this.builder = b; }
public void buildSimpleHouse() {
builder.buildWalls(4).buildDoors(1).buildWindows(2).buildRoof();
}
public void buildFamilyHouse() {
builder.buildWalls(6).buildDoors(3).buildWindows(8).buildRoof().buildGarage();
}
}
HouseBuilder builder = new HouseBuilder();
Director director = new Director();
director.setBuilder(builder);
director.buildSimpleHouse();
System.out.println(builder.getResult());
director.buildFamilyHouse();
System.out.println(builder.getResult());
House custom = new HouseBuilder()
.buildWalls(4).buildDoors(2).buildWindows(4).getResult();
System.out.println(custom);
class House
{
public int Walls, Doors, Windows;
public bool HasRoof, HasGarage;
public override string ToString() =>
$"House: {Walls} walls, {Doors} doors, {Windows} windows, " +
$"roof: {HasRoof}, garage: {HasGarage}";
}
class HouseBuilder
{
private House _house = new();
public HouseBuilder BuildWalls(int n) { _house.Walls = n; return this; }
public HouseBuilder BuildDoors(int n) { _house.Doors = n; return this; }
public HouseBuilder BuildWindows(int n) { _house.Windows = n; return this; }
public HouseBuilder BuildRoof() { _house.HasRoof = true; return this; }
public HouseBuilder BuildGarage() { _house.HasGarage = true; return this; }
public House GetResult() { var r = _house; _house = new House(); return r; }
}
class Director
{
private HouseBuilder _builder = null!;
public void SetBuilder(HouseBuilder b) => _builder = b;
public void BuildSimpleHouse() =>
_builder.BuildWalls(4).BuildDoors(1).BuildWindows(2).BuildRoof();
public void BuildFamilyHouse() =>
_builder.BuildWalls(6).BuildDoors(3).BuildWindows(8).BuildRoof().BuildGarage();
}
var builder = new HouseBuilder();
var director = new Director();
director.SetBuilder(builder);
director.BuildSimpleHouse();
Console.WriteLine(builder.GetResult());
director.BuildFamilyHouse();
Console.WriteLine(builder.GetResult());
Console.WriteLine(new HouseBuilder().BuildWalls(4).BuildDoors(2).BuildWindows(4).GetResult());
from __future__ import annotations
from abc import ABC, abstractmethod
class House:
def __init__(self) -> None:
self.walls: int = 0
self.doors: int = 0
self.windows: int = 0
self.has_roof: bool = False
self.has_garage: bool = False
def __str__(self) -> str:
return (
f"House: {self.walls} walls, {self.doors} doors, "
f"{self.windows} windows, roof: {self.has_roof}, garage: {self.has_garage}"
)
class Builder(ABC):
@abstractmethod
def build_walls(self, count: int) -> Builder: ...
@abstractmethod
def build_doors(self, count: int) -> Builder: ...
@abstractmethod
def build_windows(self, count: int) -> Builder: ...
@abstractmethod
def build_roof(self) -> Builder: ...
@abstractmethod
def build_garage(self) -> Builder: ...
class HouseBuilder(Builder):
def __init__(self) -> None:
self._house = House()
def build_walls(self, count: int) -> HouseBuilder:
self._house.walls = count
return self
def build_doors(self, count: int) -> HouseBuilder:
self._house.doors = count
return self
def build_windows(self, count: int) -> HouseBuilder:
self._house.windows = count
return self
def build_roof(self) -> HouseBuilder:
self._house.has_roof = True
return self
def build_garage(self) -> HouseBuilder:
self._house.has_garage = True
return self
def get_result(self) -> House:
result = self._house
self._house = House()
return result
class Director:
def __init__(self, builder: HouseBuilder) -> None:
self._builder = builder
def build_simple_house(self) -> None:
self._builder.build_walls(4).build_doors(1).build_windows(2).build_roof()
def build_family_house(self) -> None:
(self._builder
.build_walls(6)
.build_doors(3)
.build_windows(8)
.build_roof()
.build_garage())
builder = HouseBuilder()
director = Director(builder)
director.build_simple_house()
print(builder.get_result())
director.build_family_house()
print(builder.get_result())
Step 1 of 6
House is a plain product with no construction logic
The class just holds fields (`walls`, `doors`, `windows`, `has_roof`, `has_garage`) and a `__str__` - it has no idea how it gets assembled.
Step 2 of 6
Builder is an abstract interface for the build steps
Every step (`build_walls`, `build_doors`, `build_windows`, `build_roof`, `build_garage`) is declared `@abstractmethod`, so any concrete builder must supply all five before it can be instantiated.
Step 3 of 6
HouseBuilder starts with a fresh, empty House
`__init__` immediately creates `self._house = House()`, and `build_walls` sets one field on it before returning `self` - the pattern that makes chaining like `.build_walls(4).build_doors(1)` possible.
Step 4 of 6
get_result() hands over the house and resets for the next one
It saves the current `self._house`, replaces it with a new empty `House()`, then returns the saved one - so the same builder instance can start building the next house immediately.
Step 5 of 6
Director encodes two fixed build recipes
`build_simple_house()` and `build_family_house()` each drive the builder through a specific, fixed sequence of calls - the director owns the recipe, the builder owns each step's mechanics.
Step 6 of 6
The same builder produces two different houses via the director
`director.build_simple_house()` then `builder.get_result()` yields a small house; `build_family_house()` then `get_result()` yields a bigger one - both from the one `builder` object passed into `Director`.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- Object creation reads as a sequence of intent-revealing method calls instead of a wall of positional arguments.
- The same step sequence, run through different concrete builders, can produce entirely different product representations.
- Assembly logic is isolated in the builder, keeping the product class focused on what it represents rather than how it gets built.
- Callers only invoke the steps a particular configuration actually needs, avoiding unused parameters entirely.
Disadvantages
- Simple objects with two or three fields don't justify a builder class, a step interface, and a product - a plain constructor is clearer.
- Forgetting to call a required step produces an incomplete object at runtime rather than a compile-time error, unless the builder adds explicit validation.
- A separate Director layer adds indirection that's only worth it once the same fixed configurations get built repeatedly.
Question 1 of 10
According to the Product participant's role, must different builders share a product hierarchy?
Correct! Different builders may assemble unrelated product types through the same step interface - nothing forces them to share a hierarchy.
Not quite. Different builders may assemble unrelated product types through the same step interface - nothing forces them to share a hierarchy.
Question 2 of 10
What problem does a House constructor covering every combination of walls, doors, windows, roof, and garage run into?
Correct! Both escape routes trade one problem for another: an unreadable, error-prone giant constructor, or a subclass explosion that still can't cover every custom layout.
Not quite. Both escape routes trade one problem for another: an unreadable, error-prone giant constructor, or a subclass explosion that still can't cover every custom layout.
Question 3 of 10
In the House example, what makes a constructor covering every combination of parts unwieldy?
Correct! Most parts are optional and many depend on each other, so a constructor covering every combination explodes into new House(...) calls with half the arguments set to defaults nobody remembers the order of.
Not quite. Most parts are optional and many depend on each other, so a constructor covering every combination explodes into new House(...) calls with half the arguments set to defaults nobody remembers the order of.
Question 4 of 10
What is a stated drawback of forgetting to call a required builder step?
Correct! Unlike a constructor that forces all arguments up front, a skipped builder step just leaves that field at its default, producing an incomplete object unless the builder validates explicitly.
Not quite. Unlike a constructor that forces all arguments up front, a skipped builder step just leaves that field at its default, producing an incomplete object unless the builder validates explicitly.
Question 5 of 10
Why do the builder's step methods (buildWalls, buildDoors, etc.) return the builder itself?
Correct! Each step returns the builder so calls can be chained, and the caller skips whatever steps don't apply instead of filling in a giant parameter list.
Not quite. Each step returns the builder so calls can be chained, and the caller skips whatever steps don't apply instead of filling in a giant parameter list.
Question 6 of 10
What can two different concrete builders implementing the same step interface produce?
Correct! A HouseBuilder and a HouseBlueprintBuilder could both consume the same steps but produce a livable house object versus a printable blueprint - entirely different representations from the same sequence of calls.
Not quite. A HouseBuilder and a HouseBlueprintBuilder could both consume the same steps but produce a livable house object versus a printable blueprint - entirely different representations from the same sequence of calls.
Question 7 of 10
Which listed real-world example matches Builder's shape?
Correct! Query builders in ORMs like Knex.js and TypeORM assemble a SQL query step by step through chained calls before it's compiled and executed - the same step-by-step assembly shape as Builder.
Not quite. Query builders in ORMs like Knex.js and TypeORM assemble a SQL query step by step through chained calls before it's compiled and executed - the same step-by-step assembly shape as Builder.
Question 8 of 10
According to Builder's drawbacks, when is a builder class not worth introducing?
Correct! Simple objects with two or three fields don't justify a builder class, a step interface, and a product - a plain constructor is clearer.
Not quite. Simple objects with two or three fields don't justify a builder class, a step interface, and a product - a plain constructor is clearer.
Question 9 of 10
What role does the Director play in the Builder pattern?
Correct! A separate Director object can encode recipes like buildSimpleHouse() as fixed sequences of builder calls, but ad-hoc houses still go straight through the builder without a director.
Not quite. A separate Director object can encode recipes like buildSimpleHouse() as fixed sequences of builder calls, but ad-hoc houses still go straight through the builder without a director.
Question 10 of 10
What is the core idea of the Builder pattern?
Correct! Builder moves assembly logic into a separate builder object exposing one method per configurable part, so different configurations can be produced from the same set of steps.
Not quite. Builder moves assembly logic into a separate builder object exposing one method per configurable part, so different configurations can be produced from the same set of steps.