Low Level Design

Builder Pattern — Construct Complex Objects Step by Step

How the Builder pattern eliminates telescoping constructors, keeps object construction readable, and enables immutable objects with optional fields.

August 26, 2026·9 min read

What is the Builder Pattern?#

The Builder is a creational design pattern that separates the construction of a complex object from its representation, so the same construction process can produce different representations.

Instead of one giant constructor with 10 parameters, you chain readable method calls and call build() at the end.

You reach for Builder when:

  • An object has many optional or mandatory fields
  • Object construction involves multiple steps
  • You want to avoid telescoping constructors (a chain of overloaded constructors)
  • You need the constructed object to be immutable

Real-World Analogy#

Think of a Subway counter. You walk up and ask for a custom sub:

  1. Choose your bread
  2. Add sauces
  3. Choose toppings
  4. Optionally add cheese, jalapeños, etc.
  5. Counter (builder) assembles and hands you the complete sub

The sub object is the same structure every time, but its final composition varies based on what was chosen — and you never had to call one giant constructor with 15 arguments.


Class Diagram#


Violation Code — The Problem#

java
class Burger {
    private String bun;
    private boolean cheese;
    private boolean lettuce;
    private boolean tomato;
    private boolean mayo;
    private boolean mustard;

    // Telescoping constructors — which true is which? ❌
    public Burger(String bun) { ... }
    public Burger(String bun, boolean cheese) { ... }
    public Burger(String bun, boolean cheese, boolean lettuce) { ... }
    public Burger(String bun, boolean cheese, boolean lettuce,
                  boolean tomato, boolean mayo, boolean mustard) { ... }
}

// Usage — impossible to read
Burger b = new Burger("wheat", true, false, true, false, true);

Issues:

  1. Telescoping constructors — hard to maintain and extend
  2. Unreadable — which true is cheese? which is mayo?
  3. Can't skip optional parameters cleanly — must pass nulls/defaults
  4. Not extensible — adding one ingredient breaks all constructor signatures
  5. No runtime configuration — combinations fixed at compile time

Enhanced Code — Builder Pattern#

java
// The product — immutable after construction
public class Burger {
    private final String bun;
    private final boolean cheese;
    private final boolean lettuce;
    private final boolean tomato;
    private final boolean mayo;
    private final boolean mustard;

    // Private constructor — only BurgerBuilder can call it
    private Burger(BurgerBuilder builder) {
        this.bun      = builder.bun;
        this.cheese   = builder.cheese;
        this.lettuce  = builder.lettuce;
        this.tomato   = builder.tomato;
        this.mayo     = builder.mayo;
        this.mustard  = builder.mustard;
    }

    public void display() {
        System.out.printf("Burger[bun=%s, cheese=%b, lettuce=%b, tomato=%b, mayo=%b, mustard=%b]%n",
                bun, cheese, lettuce, tomato, mayo, mustard);
    }

    // Static inner builder
    public static class BurgerBuilder {
        private final String bun;       // required
        private boolean cheese   = false;
        private boolean lettuce  = false;
        private boolean tomato   = false;
        private boolean mayo     = false;
        private boolean mustard  = false;

        public BurgerBuilder(String bun) { this.bun = bun; }

        public BurgerBuilder addCheese()  { this.cheese  = true; return this; }
        public BurgerBuilder addLettuce() { this.lettuce = true; return this; }
        public BurgerBuilder addTomato()  { this.tomato  = true; return this; }
        public BurgerBuilder addMayo()    { this.mayo    = true; return this; }
        public BurgerBuilder addMustard() { this.mustard = true; return this; }

        public Burger build() { return new Burger(this); }
    }
}

// Usage — completely self-documenting
Burger veggieBurger = new Burger.BurgerBuilder("wholegrain")
        .addLettuce()
        .addTomato()
        .addMayo()
        .build();

Burger cheeseBurger = new Burger.BurgerBuilder("sesame")
        .addCheese()
        .addMustard()
        .build();

Common LLD Problems Using Builder Pattern#

1. User / Profile Object#

  • Context: User with many optional fields: name, email, age, address, phone, preferences.

2. SQL Query Builder#

  • Example: SelectQueryBuilder with optional WHERE, JOIN, ORDER BY, LIMIT
  • Context: Build SQL queries dynamically without string concatenation.

3. Notification Builder#

  • Example: NotificationBuilder for email, SMS, or push alerts
  • Context: Construct messages with optional sections — header, body, footer, attachments.

4. HTML / XML / JSON Document Builder#

  • Context: Build complex hierarchical documents with nested elements step by step.

5. Game Character / Avatar Builder#

  • Example: CharacterBuilder — choose name, costume, weapons, skills, attributes
  • Context: Create characters with many optional, combinable attributes.

6. Report Generator#

  • Example: ReportBuilder — compose headers, data tables, charts, summaries
  • Context: Generate complex reports with flexible, optional sections.

7. Pizza / Food Customiser#

  • Context: Build a food item step by step with crust, sauces, toppings, and extras.

8. HTTP Request Builder#

  • Example: HttpRequest.newBuilder().uri(...).header(...).POST(body).build()
  • Context: Build immutable request objects with optional headers, body, and timeout.

Builder vs Constructor Overloading#

Telescoping ConstructorsBuilder
ReadabilityPoor with many paramsSelf-documenting
Optional paramsRequires null/defaultsNatural omission
ImmutabilityPossible but messyBuilt-in
ExtensibilityAdding a field breaks all ctorsAdd one method

ReferencesLinks
Article ReferenceRefactoring Guru — Builder