Builder Pattern

Topics Covered

The Telescoping Constructor Problem

Why Default Parameters Do Not Solve This

Why Setters Are Not the Answer Either

The Builder Pattern

The Structure

A Concrete Example: Server Configuration

Before and After: The Design Shift

Fluent Builders and Method Chaining

How Method Chaining Works

The Director Pattern

When Not to Chain

Builder with Validation

Required Field Validation

Cross-Field Validation

Range and Format Validation

Collecting All Errors

Practice Problems

Imagine you are designing a User class for a web application. A user has a name, an email, and an age. Simple enough: three constructor parameters. Then requirements arrive: phone number, address, notification preferences, role, timezone, avatar URL, bio, two-factor authentication flag. Now your constructor takes 12 parameters.

This is the telescoping constructor problem. The name comes from what happens next: you write multiple constructor overloads, each adding one more parameter to the previous one, like segments of a telescope extending outward.

python
1class User:
2    def __init__(self, name, email, age=None, phone=None,
3                 address=None, notifications=True, role="user",
4                 timezone="UTC", avatar=None, bio=None,
5                 two_factor=False, max_sessions=5):
6        self.name = name
7        self.email = email
8        self.age = age
9        self.phone = phone
10        self.address = address
11        self.notifications = notifications
12        self.role = role
13        self.timezone = timezone
14        self.avatar = avatar
15        self.bio = bio
16        self.two_factor = two_factor
17        self.max_sessions = max_sessions

The creation call becomes unreadable:

python
user = User("Alice", "[email protected]", None, None, None, True,
             "admin", None, None, None, True, 30)

What does True mean in position 6? What is 30? You cannot tell without checking the constructor signature. Passing None for every unused parameter is error-prone: swap two None values and the bug is silent.

Telescoping constructors vs builder approach

Why Default Parameters Do Not Solve This

Languages with default parameters (Python, Kotlin, TypeScript) partially help: you can skip optional arguments. But the problem resurfaces when you need to set the 10th parameter without setting the 4th through 9th. In positional parameter languages like Java, you must pass every preceding argument. Even with named parameters, a constructor with 12 keyword arguments is hard to read and harder to maintain.

Why Setters Are Not the Answer Either

Another instinct is to use a no-argument constructor followed by setter calls:

python
1user = User()
2user.set_name("Alice")
3user.set_email("[email protected]")
4user.set_role("admin")

This is readable, but it creates a dangerous window: between construction and the last setter call, the object is in an incomplete state. If someone passes the User to another method before calling set_email(), that method receives an invalid object with no email. There is no compile-time or runtime guarantee that all required fields are set. The object is mutable after construction, which invites accidental modification later.

Common Pitfall

The setter approach trades one problem (unreadable constructors) for a worse one (objects that exist in invalid states). A well-designed object should be valid from the moment it is created.

The core issue is that constructors force you to provide everything at once, while setters force you to provide everything piecemeal with no validation boundary. What you need is a middle ground: a way to configure an object step by step, then produce a fully validated result in one atomic operation.