Design Patterns

Builder Design Pattern

1 week, 1 day ago ; F(visit_count) + Value(1) views
Share this

Builder Design Pattern in Python: How to Create Complex Objects Step-by-Step


Creating complex objects can be messy. If you've ever had to deal with a constructor that takes 10+ arguments, you know exactly how overwhelming it can be.

The Builder Pattern lets you construct complex objects step-by-step using a fluent API.

As a software engineer specializing in system design and software architecture, I’ve helped teams build systems that scale and are maintainable. One of the patterns that’s saved countless hours of debugging and improved code readability is the Builder Design Pattern.

This article will walk you through the motivation behind the builder pattern, how it works, and how to implement it in Python with clean, readable code.

If you’re building anything beyond trivial scripts, this design pattern will become one of your favourites.

Why Use the Builder Pattern?

Sometimes, creating objects is easy:

person = Person("John", "Doe")

But what happens when you need to configure dozens of options?

user = User("John", "Doe", 30, "Engineer", True, False, ..., ..., ...)


That’s not just ugly—it’s error-prone.

Here’s what you need to know:

  • Some objects are simple.
  • Others are complex and need multiple steps to be correctly initialized.
  • A long list of constructor arguments is hard to read and maintain.
  • build complex objects step-by-step.

It is a clean way to assemble complex objects incrementally, with control and clarity.

How It Works

It separates a complex object's construction from its representation.

Step-by-step: Basic Builder

class Person:
    def __init__(self):
        self.name = None
        self.position = None
        self.age = None

    def __str__(self):
        return f"{self.name}, {self.position}, {self.age} years old"
class PersonBuilder:
    """
    Builds a person step-by-step.
    """
    def __init__(self):
        self.person = Person()

    def called(self, name):
        self.person.name = name
        return self

    def works_as(self, position):
        self.person.position = position
        return self

    def aged(self, age):
        self.person.age = age
        return self

    def build(self):
        return self.person
# Usage
builder = PersonBuilder()
person = builder.called("Jane").works_as("Engineer").aged(30).build()
print(person)
# Output: Jane, Engineer, 30 years old

Why This Works

You set properties one at a time.
The method returns self, allowing method chaining  -a fluent interface.
Finally, build() returns the entirely constructed object.

Faceted Builder

I will use multiple builders to build multiples parts of a person- address & job

This is then managed by a single interface.

Faceted Builder in Action

class Person:
    def __init__(self):
        # address
        self.street = None
        self.city = None
        self.postcode = None
        # job
        self.company = None
        self.position = None
        self.salary = None

    def __str__(self):
        return (f"Lives at {self.street}, {self.city} {self.postcode} and "
                f"works at {self.company} as a {self.position} earning {self.salary}")

 

class PersonBuilder:
    def __init__(self):
        self.person = Person()

    @property
    def lives(self):
        return PersonAddressBuilder(self.person)

    @property
    def works(self):
        return PersonJobBuilder(self.person)

    def build(self):
        return self.person
class PersonAddressBuilder(PersonBuilder):
    def __init__(self, person):
        super().__init__()
        self.person = person

    def at(self, street):
        self.person.street = street
        return self

    def in_city(self, city):
        self.person.city = city
        return self

    def with_postcode(self, postcode):
        self.person.postcode = postcode
        return self
class PersonJobBuilder(PersonBuilder):
    def __init__(self, person):
        super().__init__()
        self.person = person

    def at(self, company):
        self.person.company = company
        return self

    def as_a(self, position):
        self.person.position = position
        return self

    def earning(self, salary):
        self.person.salary = salary
        return self

 

# Usage
pb = PersonBuilder()
p  = pb.lives.at("Ungwaro").in_city("Nairobi").with_postcode("00100") \
      .works.at("PythonHaven Ltd").as_a("SE").earning(120000).build()

print(p)

# Lives at Ungwaro, Nairobi 00100 and works at PythonHaven Ltd as a SE earning 120000


Why Faceted Builder?

  • Complexity-> manageable pieces.
  • Each piece (facet) ->clear, focused responsibility.
  • Everything builds into one final object.

Use This When Object Creation Gets Out of Hand

You don’t need the builder pattern for simple constructors. But when you:

Need many optional parameters
Want readable and fluent object construction
Are initializing objects across multiple responsibilities (job, address, preferences, etc.)


The Builder Pattern helps you stay clean, DRY, and maintainable.


Benefits

  • Cleaner code
  • Easier to maintain
  • Supports immutability and step-by-step creation


Ready to Try This Pattern in Your Code?

The next time you find yourself writing a constructor with more than a handful of arguments—or worse, multiple constructors with different permutations—reach for the Builder Pattern.

Clean code is easier to reason about and debug. With this pattern, you're not just writing code; you're designing it.

Want to master more patterns like this? Check out Python Design Patterns for more actionable techniques.

 

 

Become a member
Get the latest news right in your inbox. We never spam!

Read next

How to Solve the Two-Sum Problem in Python

How to Solve the Two-Sum Problem in Python The Two Sum Problem is a common challenge in cod… Read More

9 hours, 42 minutes ago . 104 views