Creating a simple Python object is easy:
user = User("John", "john@example.com")
But what happens when an object has many optional properties?
For example:
User
├── name
├── email
├── phone
├── address
├── age
├── profile_image
├── newsletter
├── notifications
└── two_factor_authentication
A constructor with many parameters quickly becomes difficult to read and maintain.
The Builder Design Pattern solves this type of problem by allowing complex objects to be constructed step by step.
Refactoring.Guru describes Builder as a creational pattern that constructs complex objects step by step and allows different representations to be produced using the same construction process.
What Is the Builder Pattern?
Instead of creating an object with a huge constructor:
pizza = Pizza(
"large",
True,
True,
False,
True,
False,
True,
True
)
we can build it step by step:
pizza = (
PizzaBuilder()
.set_size("large")
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.add_spicy_sauce()
.build()
)
This makes the construction process much easier to understand.
Pizza Example
A pizza can have many optional properties:
- Size
- Cheese
- Pepperoni
- Mushrooms
- Onions
- Olives
- Extra Cheese
- Spicy Sauce
Instead of creating a constructor with every possible option, we create a builder.
class Pizza:
def __init__(
self,
size,
cheese=False,
pepperoni=False,
mushrooms=False,
onions=False,
olives=False,
extra_cheese=False,
spicy=False
):
self.size = size
self.cheese = cheese
self.pepperoni = pepperoni
self.mushrooms = mushrooms
self.onions = onions
self.olives = olives
self.extra_cheese = extra_cheese
self.spicy = spicy
Now create the builder:
class PizzaBuilder:
def __init__(self):
self.size = "medium"
self.cheese = False
self.pepperoni = False
self.mushrooms = False
self.onions = False
self.olives = False
self.extra_cheese = False
self.spicy = False
def set_size(self, size):
self.size = size
return self
def add_cheese(self):
self.cheese = True
return self
def add_pepperoni(self):
self.pepperoni = True
return self
def add_mushrooms(self):
self.mushrooms = True
return self
def add_onions(self):
self.onions = True
return self
def add_olives(self):
self.olives = True
return self
def add_extra_cheese(self):
self.extra_cheese = True
return self
def make_spicy(self):
self.spicy = True
return self
def build(self):
return Pizza(
self.size,
self.cheese,
self.pepperoni,
self.mushrooms,
self.onions,
self.olives,
self.extra_cheese,
self.spicy
)
Now we can create different pizzas:
pizza = (
PizzaBuilder()
.set_size("large")
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.make_spicy()
.build()
)
The code clearly communicates how the object is being constructed.
Why Is This Better?
Compare:
Pizza(
"large",
True,
True,
True,
False,
False,
True,
True
)
with:
(
PizzaBuilder()
.set_size("large")
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.add_extra_cheese()
.make_spicy()
.build()
)
The second version is much easier to understand. You immediately know what each option means.
Another Example: Building a House
A house may contain:
- Walls
- Doors
- Windows
- Roof
- Garage
- Swimming Pool
- Garden
- Security System
Creating every possible combination through constructors or subclasses can become complicated.
A Builder can expose steps such as:
builder.build_walls()
builder.build_doors()
builder.build_windows()
builder.build_roof()
builder.build_garage()
Only the required steps need to be executed.
This matches the motivation described by Refactoring.Guru: complex construction with many optional parameters can otherwise lead to large constructors or excessive subclasses.
Fluent Interface
Builder implementations in Python often use method chaining:
builder.set_size("large").add_cheese().add_pepperoni()
This is called a fluent interface. Each method returns self:
return self
This allows the next method to be called immediately.
Builder Without a Separate Director
In the classic Builder pattern, a Director can control the construction sequence.
For example:
Director
|
v
Builder
|
v
Product
The Director can define standard construction processes. However, in Python applications, a separate Director is not always necessary. The client can directly use the builder when the construction process is simple.
Advantages
Readable Object Construction — The code clearly shows which options are selected.
Handles Optional Parameters — You only specify the properties you actually need.
Flexible Construction — Different configurations can be created using the same builder.
Separation of Concerns — Object construction logic is separated from the final product.
When Should You Use Builder?
Builder is particularly useful when:
- An object has many optional parameters
- Construction involves multiple steps
- Different configurations of the same object are required
- Constructors are becoming difficult to read
- You want readable and maintainable object creation code
Conclusion
The Builder pattern focuses on how a complex object is constructed.
The key idea is: build a complex object step by step instead of forcing the client to provide everything through one complicated constructor.
For Python applications, Builder can be useful for pizzas, HTTP requests, configuration objects, reports, documents, API payloads, and other objects with many optional settings.