Logo Sujal Magar
Builder Pattern

Builder Pattern

February 18, 2026
6 min read
Table of Contents

1. Introduction

The Builder pattern (also known as Generator) is one of the most widely used Creational Design Patterns. It separates the construction of a complex object from its representation, allowing the same construction process to create different representations. This pattern is especially powerful when you need to assemble objects with many optional or configurable components, while keeping the client code simple, readable, and independent of the intricate construction logic.

It enables adherence to the Open-Closed Principle (open for extension, closed for modification) and helps avoid issues like telescoping constructors or mutable state during object assembly.


2. Code Example

Bad Approach

Hardcoded Conditional Creation (Tight Coupling & Violation of OCP)

class House:
  def __init__(
    self,
    walls: int = 0,
    doors: int = 0,
    windows: int = 0,
    roof: bool = False,
    garage: bool = False,
    pool: bool = False,
  ):
    self.walls = walls
    self.doors = doors
    self.windows = windows
    self.roof = roof
    self.garage = garage
    self.pool = pool
 
  def describe(self) -> str:
    return f"House with {self.walls} walls, {self.doors} doors, {self.windows} windows, roof: {self.roof}, garage: {self.garage}, pool: {self.pool}"
 
 
# Client code - fragile and violates open-closed principle
basic_house = House(walls=4, doors=2, windows=4, roof=True)
luxury_house = House(walls=6, doors=4, windows=8, roof=True, garage=True, pool=True)
print(basic_house.describe())   # House with 4 walls, 2 doors, 4 windows, roof: True, garage: False, pool: False
print(luxury_house.describe())  # House with 6 walls, 4 doors, 8 windows, roof: True, garage: True, pool: True

Problems:

  • Telescoping constructor: Adding new features (e.g., garden) requires modifying the init signature, breaking existing code
  • High parameter count leads to unreadable calls and error-prone defaults
  • Client is tightly coupled to construction details and must know all options upfront

Good Approach

Builder Pattern (Step-by-Step Construction)

from abc import ABC, abstractmethod
 
# Product - the complex object
class House:
  def __init__(self):
    self.walls = 0
    self.doors = 0
    self.windows = 0
    self.roof = False
    self.garage = False
    self.pool = False
 
  def describe(self) -> str:
    return f"House with {self.walls} walls, {self.doors} doors, {self.windows} windows, roof: {self.roof}, garage: {self.garage}, pool: {self.pool}"
 
# Abstract Builder - declares steps for building the product
class HouseBuilder(ABC):
  @abstractmethod
  def reset(self):
    pass
 
  @abstractmethod
  def build_walls(self):
    pass
 
  @abstractmethod
  def build_doors(self):
    pass
 
  @abstractmethod
  def build_windows(self):
    pass
 
  @abstractmethod
  def build_roof(self):
    pass
 
  @abstractmethod
  def build_garage(self):
    pass
 
  @abstractmethod
  def build_pool(self):
    pass
 
  @abstractmethod
  def get_result(self) -> House:
    pass
 
# Concrete Builder - implements the steps
class ConcreteHouseBuilder(HouseBuilder):
  def __init__(self):
    self.reset()
 
  def reset(self):
    self.house = House()
 
  def build_walls(self):
    self.house.walls = 4  # Default, can be customized
 
  def build_doors(self):
    self.house.doors = 2
 
  def build_windows(self):
    self.house.windows = 4
 
  def build_roof(self):
    self.house.roof = True
 
  def build_garage(self):
    self.house.garage = True
 
  def build_pool(self):
    self.house.pool = True
 
  def get_result(self) -> House:
    return self.house
 
 
# Director - optional, orchestrates the building process
class Director:
  def __init__(self, builder: HouseBuilder):
    self.builder = builder
 
  def construct_basic_house(self):
    self.builder.reset()
    self.builder.build_walls()
    self.builder.build_doors()
    self.builder.build_windows()
    self.builder.build_roof()
 
  def construct_luxury_house(self):
    self.builder.reset()
    self.builder.build_walls()
    self.builder.build_doors()
    self.builder.build_windows()
    self.builder.build_roof()
    self.builder.build_garage()
    self.builder.build_pool()
 
# Client - depends only on abstraction
builder = ConcreteHouseBuilder()
director = Director(builder)
 
director.construct_basic_house()
basic_house = builder.get_result()
 
print(basic_house.describe())   # House with 4 walls, 2 doors, 4 windows, roof: True, garage: False, pool: False
 
director.construct_luxury_house()
luxury_house = builder.get_result()
 
print(luxury_house.describe())  # House with 4 walls, 2 doors, 4 windows, roof: True, garage: True, pool: True

Adding a new feature (e.g., garden) requires only adding methods to the builder interface and implementations (no changes to existing client or product code).


3. Complexities & Coupling Reduced/Solved

ProblemHow Builder HelpsBenefit Level
Tight coupling to construction detailsClient interacts via simple director or fluent builder methods, unaware of internalsHigh
Telescoping constructors/parameter explosionReplaces long init lists with step-by-step methods for optional partsHigh
Violation of Open-Closed PrincipleNew features added by extending builder without modifying existing classesVery High
Duplicated or scattered assembly logicConstruction centralized in builder, reusable across representationsMedium-High
Difficult testabilityEasy to mock/substitute builders from different configurationsHigh
High cyclomatic complexity in creationBreaks complex init into simple, sequential stepsHigh

4. When to Use Builder

  • You need to construct complex objects with many optional or configurable parts (e.g., avoiding massive constructors)
  • The same construction process should create different representations or variants of the object
  • You want step-by-step assembly to make code more readable and flexible (e.g., fluent interfaces)
  • Building immutable objects where parameters are set incrementally before finalization
  • Systems with director classes to orchestrate common build recipes

Common real-world examples:

  • String builders (e.g., Java’s StringBuilder for efficient string assembly)
  • GUI form builders (adding labels, buttons, fields step-by-step)
  • Meal/order builders (e.g., burger with optional toppings, sides)
  • Configuration objects (e.g., HTTP requests with headers, params, body)

5. When Not to Use Builder

Avoid when:

  • Object creation is simple (few parameters, no variation)
  • You have a fixed, small set of configurations without need for steps
  • You are writing a very small script/prototype where fluency adds unnecessary classes
  • The object is mutable and doesn’t require immutability or complex assembly (prefer setters)
  • Performance-critical paths where extra method calls introduce overhead
  • Overlapping with other patterns (e.g., if family consistency is key, prefer Abstract Factory)
  • You are tempted to use it just because “it’s a creational pattern” (this leads to over-abstraction)