Builder Pattern — Core Concepts
The problem it solves
Some objects need many configuration options. A database query might have filters, sorting, pagination, joins, and projections. A report might have a title, columns, grouping, formatting rules, and export settings. When you cram all that into a constructor, you get what’s called a “telescoping constructor” — a parameter list so long that nobody remembers which argument is which.
The Builder Pattern separates construction from representation. You build an object through a sequence of named method calls, then finalize it when ready.
How it works in Python
Python’s flexibility means you don’t need the formal Director/Builder/ConcreteBuilder hierarchy from the Gang of Four book. The most common Pythonic approach uses method chaining — each setter method returns self so calls can be linked together.
A typical structure:
- Builder class holds intermediate state as instance attributes
- Configuration methods set one aspect and return
self - Build method validates the accumulated state and returns the finished object
When to use it
- Objects with more than 4-5 optional parameters
- Construction requires validation across multiple fields together
- You want to enforce “required vs optional” without keyword-argument sprawl
- Different configurations of the same object appear frequently (e.g., test fixtures)
When to skip it
- Simple objects where a dataclass or named tuple is sufficient
- Objects with no optional fields — a plain constructor works fine
- When Python’s keyword arguments already give enough clarity
A simple mental model
Think of building a query:
- Start with a table name —
QueryBuilder("users") - Pick columns —
.select("name", "email") - Add filters —
.where("active = true") - Set ordering —
.order("created_at") - Finalize —
.build()
Each step returns the builder, so you can chain them. The final .build() validates everything and produces the immutable result. If you forgot to select columns, .build() raises an error rather than producing a broken query.
Common misconception
“Builder Pattern is just __init__ with defaults.” Not quite. The key difference is deferred construction. A builder accumulates state across multiple calls, validates consistency, and only produces the final object when you call .build(). This lets you catch invalid combinations (like setting both ascending and descending sort) before the object exists.
How it compares to other patterns
| Pattern | Purpose |
|---|---|
| Factory | Chooses which class to instantiate |
| Builder | Controls how an instance is configured |
| Prototype | Copies an existing instance instead of building fresh |
Builders and factories often work together — a factory might use a builder internally to construct the chosen type.
The one thing to remember: Use a builder when construction is complex enough that a single constructor call would be confusing — it makes each configuration decision explicit and named.
See Also
- Python Adapter Pattern How Python's Adapter Pattern works like a travel power plug — making incompatible things work together.
- Python Bridge Pattern Why separating what something does from how it does it keeps your Python code from becoming a tangled mess.
- Python Composite Pattern How the Composite Pattern lets you treat a group of things the same way you'd treat a single thing in Python.
- Python Facade Pattern How the Facade Pattern gives you one simple button instead of a confusing control panel in Python.
- Python Flyweight Pattern How the Flyweight Pattern saves memory by sharing common data instead of copying it thousands of times.