Use when constructing a complex object requires many steps or parameters — especially when the same construction process should produce different representations, or when telescoping constructors become unreadable.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add jeffreytse/grimoire-core --skill apply-builder-pattern --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Apply Builder Pattern?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeffreytse-apply-builder-pattern)More formats (shields.io, HTML) on the badges page.
---
name: apply-builder-pattern
description: Use when constructing a complex object requires many steps or parameters — especially when the same construction process should produce different representations, or when telescoping constructors become unreadable.
source: "Gamma, Helm, Johnson, Vlissides, \"Design Patterns: Elements of Reusable Object-Oriented Software\" (1994) pp. 97–106; widely adopted in Java (StringBuilder, Lombok @Builder), Python (dataclasses + builder idiom), Kotlin (apply/also scope functions), Go (functional options)"
tags: [design-patterns, creational, builder, oop, developer, readability, complex-construction]
related: [apply-abstract-factory-pattern, apply-factory-method-pattern]
---
# Apply Builder Pattern
Separate the construction of a complex object from its representation so the same process can create different representations.
## Why This Is Best Practice
**Adopted by:** Java's `StringBuilder` (the most-used Java class by call frequency),
Lombok `@Builder` (used in >50% of large Java codebases per JetBrains developer survey
2023), Kotlin's `buildString`/`buildList`, Google Protocol Buffers (`.newBuilder()`),
and Go's functional options pattern — a Builder variant adopted as idiomatic Go.
**Impact:** Telescoping constructors (10-parameter constructors) are cited in Clean Code
(Martin, 2008) as one of the top readability defects in Java codebases. The Builder
pattern eliminates this directly: Apache HttpClient's request builder reduced a
12-parameter constructor to a fluent 4-step build chain, eliminating a documented class
of parameter-order bugs in their public API.
**Why best:** The alternative — telescoping constructors or a large configuration map —
either forces callers to pass nulls/defaults for unused fields (error-prone), or loses
type safety (map-based config). Builder provides named, typed, optional parameters
without changing the target object's constructor signature.
Sources: Gamma et al. (1994) pp. 97–106; Martin, "Clean Code" (2008) ch. 3;
Lombok @Builder documentation; Google Protocol Buffers design docs
## Steps
### Step 1: Define the product class with private constructor
```python
class Pizza:
def __init__(self, size: str, cheese: bool, pepperoni: bool, mushrooms: bool):
self.size = size
self.cheese = cheese
self.pepperoni = pepperoni
self.mushrooms = mushrooms
def __repr__(self):
toppings = [t for t, v in [("cheese", self.cheese),
("pepperoni", self.pepperoni),
("mushrooms", self.mushrooms)] if v]
return f"Pizza({self.size}, {', '.join(toppings) or 'plain'})"
```
### Step 2: Create the builder class with setter methods that return self
```python
class PizzaBuilder:
def __init__(self, size: str):
self._size = size
self._cheese = False
self._pepperoni = False
self._mushrooms = False
def with_cheese(self) -> "PizzaBuilder":
self._cheese = True
return self
def with_pepperoni(self) -> "PizzaBuilder":
self._pepperoni = True
return self
def with_mushrooms(self) -> "PizzaBuilder":
self._mushrooms = True
return self
def build(self) -> Pizza:
return Pizza(self._size, self._cheese, self._pepperoni, self._mushrooms)
```
### Step 3: Use fluent chaining at the call site
```python
# Before — telescoping constructor, unclear which True is which
pizza = Pizza("large", True, False, True)
# After — self-documenting, order-independent
pizza = (PizzaBuilder("large")
.with_cheese()
.with_mushrooms()
.build())
```
### Step 4: Validate in build(), not in setters
Put validation — required fields, mutually exclusive options — in `build()`, not in
each setter. This lets callers construct the builder in any order before committing.
```python
def build(self) -> Pizza:
if not self._size:
raise ValueError("size is required")
return Pizza(self._size, self._cheese, self._pepperoni, self._mushrooms)
```
### Step 5: For different representations, use multiple concrete builders
```python
class ThinCrustBuilder(PizzaBuilder): ...
class DeepDishBuilder(PizzaBuilder): ...
director = PizzaDirector()
director.build_margherita(ThinCrustBuilder("medium"))
director.build_margherita(DeepDishBuilder("large"))
```
## When NOT to Use
- **Simple objects with few fields** — a 2-parameter constructor is cleaner than a builder. The pattern pays off at 4+ optional parameters.
- **Immutable value objects with required fields only** — use a plain constructor or `dataclass`. Builder adds overhead for no benefit.
- **Performance-critical hot paths** — builder instances add allocation cost. For objects created millions of times per second, measure before adding a builder.
## Common Mistakes
**Forgetting to call `build()`** — callers get a Builder instead of the product. In typed languages, the return type prevents this. Document it in the builder's docstring.
**Mutable builders shared across threads** — a Builder holds mutable intermediate state. Don't share Builder instances between threads without synchronization.
**Validating in setters instead of `build()`** — validating in setters forces call order and prevents deferred construction. Validate at build time only.Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!