ReviseAlgo Logo

Creational Patterns

Builder

Construct complex objects step-by-step using a fluent interface with method chaining

Last Updated: June 26, 2026 23 min read

The Builder Pattern is a creational design pattern that separates the construction of a complex object from its representation. It allows the same construction process to create different representations, providing a clean, readable fluent interface for assembling objects with numerous mandatory and optional configuration parameters.

1. Learning Objectives

  • Identify when to apply the Builder Pattern to solve construction issues.
  • Understand the trade-offs of the Telescoping Constructor and JavaBean Setter anti-patterns.
  • Implement fluent method-chaining syntax in Java, Python, and C++.
  • Model objects with step-by-step validation during instantiation.
  • Construct immutable objects with numerous optional configuration fields.

2. Problem & Naive Solution

Consider a class representing a high-performance Computer. It requires a few mandatory parameters (e.g. CPU, RAM) and supports numerous optional components (e.g. GraphicsCard, Storage, Bluetooth, OperatingSystem, LiquidCooling).

The Telescoping Constructor Anti-Pattern

To handle optional configurations, developers often write multiple constructor overloads:

This design introduces significant issues:

  • Unreadability: Client calls look like new Computer("Intel i9", "32GB", null, "1TB", false). The developer must memorize parameter positions and type mappings.
  • Maintenance Overhead: Adding a new optional field requires creating several new constructor overloads.

3. Issues with the JavaBean Setter Approach

An alternative naive approach is to use a default constructor and call setter methods:

This approach has two major weaknesses:

  • Loss of Immutability: The object must expose public setters, leaving it vulnerable to modification at runtime. In concurrent systems, mutable objects are prone to race conditions.
  • Inconsistent Object State: The computer is in an incomplete, invalid state between the new Computer() call and the final setter invocation. If another thread accesses the object mid-initialization, it will read corrupt or partial data.

4. Pattern Introduction & UML

The Builder pattern separates construction from the class definition:

  • Target Product: The complex object being built (often has a private constructor to restrict instantiation).
  • Builder Class: A helper class (frequently nested) that mirrors the product's attributes, exposing fluent setter methods.
  • Fluent Methods: Configuration methods that return a reference to the builder itself, allowing method chaining.
  • Build Method: The final method (e.g. .build()) that validates constraints and instantiates the immutable target product.

5. Participants

  • Product (Computer): The complex target object with private constructor and final fields.
  • Builder (ComputerBuilder): The helper class that collects fields and instantiates the product.
  • Director (Optional): A class that defines the steps to build specific product configurations (e.g., buildGamingComputer(), buildOfficeLaptop()). In modern practice, developers often omit the director, choosing to chain builder calls directly.

6. Theory

The Builder pattern implements a Fluent Interface utilizing Method Chaining. Because each configuration method returns a reference to the builder object (this), calls can be chained together in a single statement.

Importantly, validation logic runs inside the final build() method. This ensures that the final product is instantiated only if all fields form a valid configuration (e.g. validating that a computer has an adequate power supply to run its graphics card), preventing invalid states.

7. Syntax Explanation

Chaining syntax requires returning a reference to the current builder instance:

  • Java: Methods return this (e.g., public ComputerBuilder setGpu(String gpu) { this.gpu = gpu; return this; }).
  • Python: Methods return self (e.g., def set_gpu(self, gpu): self.gpu = gpu; return self).
  • C++: Methods return a reference (e.g., ComputerBuilder& setGpu(std::string gpu) { this->gpu = gpu; return *this; }). Returning a reference avoids object copying.

8. Step-by-Step Implementation

  1. Step 1: Create the target Product class with final instance fields and a private constructor.
  2. Step 2: Create a static nested Builder class inside the Product.
  3. Step 3: Define a builder constructor that accepts only the mandatory fields, setting them as final.
  4. Step 4: Add configuration methods for the optional fields. Each method updates the builder's state and returns the builder reference.
  5. Step 5: Implement the build() method. Validate the collected parameters and call the private Product constructor, passing the builder as an argument.

9. Complete Code (Mini Project)

10. Code Walkthrough

Let's review the code structure:

  • The Computer class constructor is private. Clients cannot call new Computer(...) directly, forcing them to use the builder.
  • The nested class ComputerBuilder collects parameters. Fields like cpu and ram are passed directly to the builder constructor, marking them as mandatory.
  • Optional fields have default values (e.g. liquidCooling = false) to ensure the product compiles in a valid state even if the client skips these configuration calls.
  • Chaining methods return ComputerBuilder& (C++) or ComputerBuilder (Java), updating the fields and returning this.
  • The build() method acts as a guard. If a client attempts to configure liquid cooling without a graphics card, it throws an exception, preventing the instantiation of an invalid object.

11. Execution Flow

  1. Builder Instantiation: Client allocates a ComputerBuilder instance on the heap, passing mandatory CPU and RAM arguments.
  2. Configuration Chaining: The client calls setGpu(). The builder updates its field and returns its own reference, which is then used to call setStorage() in a chained sequence.
  3. Validation & Object Creation: The client calls build(). The builder validates constraints. If valid, it invokes the private product constructor, passing its state values, and returns the constructed Computer object.

12. Internal Working (Memory Allocation)

Using the Builder pattern introduces a small memory allocation overhead:

  • Builder Lifecycle: The ComputerBuilder is a temporary helper object allocated on the heap to collect attributes.
  • Product Lifecycle: Once build() is called, the final Computer is allocated on the heap, copying the builder's state fields. The temporary builder object is dereferenced and becomes eligible for Garbage Collection.
  • Garbage Collection Overhead: In performance-critical loops (e.g. instantiating millions of objects per second), creating a temporary builder for every product instance increases garbage collection overhead. In standard business applications, however, this overhead is negligible.

13. Complexity Analysis

  • Time Complexity: $O(1)$ constant time for setting attributes and instantiating the target product.
  • Space Complexity: $O(1)$ constant space. We allocate one helper builder object and one target product object.

14. Best Practices

  • Keep products immutable: Declare all product attributes as final (or read-only properties) and do not provide setter methods on the product class.
  • Validate inside build(): Perform complex business rules validation inside build() to ensure the product is never instantiated in an invalid state.
  • Nest the Builder class: Nest the builder inside the product class. This groups the code logically and allows the builder to access the product's private constructor.

15. Common Mistakes

  • Forgetting to return the builder reference: Writing setter methods that return void instead of this, which breaks method chaining.
  • Allowing external modification: Exposing public constructors or setters on the product class, defeating the purpose of using a builder.
  • Over-engineering simple classes: Using the Builder pattern for small classes with only two or three attributes. A simple constructor is cleaner in these cases.

16. Framework Usage

  • Java's StringBuilder: Operates as a builder for strings, allowing developers to append characters and construct the final string via toString().
  • Project Lombok's @Builder: A Java library annotation that automatically generates the static nested builder class and fluent setters at compile time, eliminating boilerplate code.
  • HttpRequest.newBuilder(): Java 11's HTTP client uses the Builder pattern to construct immutable HTTP requests (headers, body, method types).

17. Interview Tips

  • Factory vs. Builder: Factory creates a product in a single call (e.g. createProduct()). Builder constructs complex products step-by-step using method chaining.
  • Telescoping Constructor: Be prepared to define and explain the telescoping constructor anti-pattern (overloaded constructors with progressively more parameters).
  • Validation: Emphasize that validation should happen in build() just before instantiating the product to guarantee the product starts in a valid state.

18. Practice Exercises

  • Easy: Write a simple PizzaBuilder in Python that supports choosing crust size, cheese toppings, and meat toppings.
  • Medium: Design a fluent SQLQueryBuilder in Java that builds a query string (e.g. select, from, where, join) step-by-step.
  • Hard: Create a C++ NetworkPacketBuilder that constructs a network package (header, payload, checksum). Enforce validation to verify the checksum is calculated and payload matches length attributes before building.

19. Challenge Problem

Design an HTML Document Generator using the Builder Pattern. The generator should construct complex HTML elements (header, paragraph, list, table) step-by-step. The document should support custom CSS styling links and validation to ensure that closing tags match opening tags. Write the implementation code in Java, Python, or C++ and show how the builder constructs a clean, formatted HTML string.

20. Summary & Cheat Sheet

  • The Builder pattern separates the construction of a complex object from its class definition.
  • It avoids the telescoping constructor and JavaBean setter anti-patterns.
  • Fluent methods return this or a reference to enable method chaining.
  • Validation inside build() prevents instantiating objects in invalid states.

21. Quiz

1. Which issue is solved by the Builder Pattern?

A) Global instance access race conditions
B) The telescoping constructor anti-pattern (Correct)
C) Subclass method overrides throwing exceptions

2. Why does the JavaBean setter approach violate clean object-oriented design?

A) It does not support private fields
B) It prevents method overloading
C) It makes objects mutable and prone to inconsistent states (Correct)

3. What does a fluent method in a builder class return?

A) A copy of the final product
B) A reference to the builder instance (this/self) (Correct)
C) void

4. Where should complex business logic validation happen in the Builder pattern?

A) Inside the private constructor of the product class
B) Inside the build() method of the builder class (Correct)
C) Inside the client code before calling the builder

5. How does a nested builder access the product's private constructor in Java?

A) Via classloader reflection
B) Java inner/nested classes can access private constructors of their outer class directly (Correct)
C) Outer class constructors cannot be private in Java

6. What is a key disadvantage of the Builder Pattern?

A) It requires duplicate field declarations, adding allocation overhead for the builder instance (Correct)
B) It slows down class loading times
C) It breaks inheritance structures

7. What is the role of Lombok's @Builder annotation?

A) It automatically compiles the code into binary files
B) It generates the builder class and fluent setters at compile time (Correct)
C) It locks the object database connection pool

8. Can the Builder Pattern create different representations of the same object?

A) Yes, different concrete builders can implement the same interface to build different representations (Correct)
B) No, a builder is hardcoded to construct a single class
C) Only if they share the same classloader

9. In C++, why do fluent methods return references (ComputerBuilder&)?

A) To prevent compiler memory leaks
B) To enable method chaining without creating duplicate object copies in memory (Correct)
C) To enable dynamic virtual casting

10. What class in the Java Standard Library uses the Builder pattern to construct string values?

A) StringTokenizer
B) StringBuilder (Correct)
C) StringFormatter

22. Next Lesson Preview

In the next lesson, we will cover the Factory Method Pattern. We will learn how to define an interface for creating objects, allowing subclasses to decide which class to instantiate!