Skip to content
intermediatePhase 49 · Low-Level Design

Builder Pattern

Construct complex objects step by step with readable code.

30m
0 problems
Topic Progress0%

Why Builder

The Builder pattern solves the problem of creating complex objects with many optional parameters.

The Problem

// Constructor with too many parameters
public User(String name, String email, int age, String phone,
            String address, String city, String state, String zip,
            Role role, List<String> permissions, Date createdAt,
            boolean active) { ... }

// Which parameter is which?
User user = new User("John", "john@email.com", 30, null,
                    "123 Main St", "NYC", "NY", "10001",
                    Role.ADMIN, null, null, true);
// Is null the phone or permissions?

The Solution

User user = new User.Builder()
    .name("John")
    .email("john@email.com")
    .age(30)
    .address("123 Main St")
    .city("NYC")
    .state("NY")
    .zip("10001")
    .role(Role.ADMIN)
    .active(true)
    .build();

Why Builder Matters

  1. Readability: Named parameters are self-documenting
  2. Flexibility: Only set parameters you need
  3. Immutability: Can create immutable objects with builders
  4. Validation: Validate during build, not in constructor
  5. Reduced errors: No parameter order confusion

Builder Implementation

A complete Builder implementation with validation and immutability.

Standard Builder

public class User {
    private final String name;       // Required
    private final String email;      // Required
    private final int age;           // Optional
    private final String phone;      // Optional
    private final String address;    // Optional
    
    // Private constructor takes builder
    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }
    
    // Getters only (immutable)
    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }
    
    // Static nested Builder class
    public static class Builder {
        // Required parameters
        private final String name;
        private final String email;
        
        // Optional parameters with defaults
        private int age = 0;
        private String phone = null;
        private String address = null;
        
        public Builder(String name, String email) {
            this.name = name;
            this.email = email;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;  // Return this for chaining
        }
        
        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }
        
        public Builder address(String address) {
            this.address = address;
            return this;
        }
        
        public User build() {
            // Validation
            if (name == null || name.isEmpty()) {
                throw new IllegalStateException("Name is required");
            }
            if (!email.contains("@")) {
                throw new IllegalStateException("Invalid email");
            }
            return new User(this);
        }
    }
}

Usage

User user = new User.Builder("John", "john@email.com")
    .age(30)
    .phone("555-1234")
    .address("123 Main St")
    .build();

Validation in Builder

public User build() {
    // Validate required fields
    Objects.requireNonNull(name, "Name cannot be null");
    Objects.requireNonNull(email, "Email cannot be null");
    
    // Validate business rules
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Invalid age: " + age);
    }
    
    if (!email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
        throw new IllegalArgumentException("Invalid email format");
    }
    
    return new User(this);
}

Fluent Interface

Fluent interfaces make builders readable through method chaining.

Fluent Interface Basics

// Each method returns 'this' for chaining
public class QueryBuilder {
    private String table;
    private List<String> conditions = new ArrayList<>();
    private List<String> orderBy = new ArrayList<>();
    private Integer limit;
    
    public QueryBuilder from(String table) {
        this.table = table;
        return this;
    }
    
    public QueryBuilder where(String condition) {
        conditions.add(condition);
        return this;
    }
    
    public QueryBuilder orderBy(String column) {
        orderBy.add(column);
        return this;
    }
    
    public QueryBuilder limit(int limit) {
        this.limit = limit;
        return this;
    }
    
    public String build() {
        StringBuilder sb = new StringBuilder("SELECT * FROM ").append(table);
        if (!conditions.isEmpty()) {
            sb.append(" WHERE ").append(String.join(" AND ", conditions));
        }
        if (!orderBy.isEmpty()) {
            sb.append(" ORDER BY ").append(String.join(", ", orderBy));
        }
        if (limit != null) {
            sb.append(" LIMIT ").append(limit);
        }
        return sb.toString();
    }
}

// Usage
String query = new QueryBuilder()
    .from("users")
    .where("age > 18")
    .where("active = true")
    .orderBy("name")
    .limit(10)
    .build();

Java Standard Library Builders

// StringBuilder
String result = new StringBuilder()
    .append("Hello")
    .append(" ")
    .append("World")
    .toString();

// Stream builder
Stream<String> stream = Stream.<String>builder()
    .add("a")
    .add("b")
    .add("c")
    .build();

// ProcessBuilder
Process process = new ProcessBuilder()
    .command("ls", "-la")
    .directory(new File("/tmp"))
    .redirectErrorStream(true)
    .start();

Fluent Interface with Validation

public class ServerBuilder {
    private String host;
    private int port;
    private Duration timeout;
    private int maxRetries;
    
    public ServerBuilder host(String host) {
        this.host = Objects.requireNonNull(host);
        return this;
    }
    
    public ServerBuilder port(int port) {
        if (port < 1 || port > 65535) {
            throw new IllegalArgumentException("Invalid port");
        }
        this.port = port;
        return this;
    }
    
    public ServerBuilder timeout(Duration timeout) {
        this.timeout = Objects.requireNonNull(timeout);
        return this;
    }
    
    public ServerBuilder maxRetries(int maxRetries) {
        if (maxRetries < 0) {
            throw new IllegalArgumentException("Max retries cannot be negative");
        }
        this.maxRetries = maxRetries;
        return this;
    }
    
    public Server build() {
        // Validate required fields
        Objects.requireNonNull(host, "Host is required");
        if (port == 0) throw new IllegalStateException("Port is required");
        return new Server(this);
    }
}

Practice Problems

0/3solved
Design Builder Pattern System

Design a scalable Builder Pattern system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Builder Pattern Scaling

How would you scale Builder Pattern to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Builder Pattern Failure Modes

Analyze potential failure modes for Builder Pattern and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What problem does the Builder pattern solve?

Question 1 options

2. What does each Builder method return to enable chaining?

Question 2 options

3. Why is validation done in the Builder's build() method?

Question 3 options

4. What is a fluent interface?

Question 4 options

5. What is the difference between Builder and Factory?

Question 5 options

Flashcards

Question

What is the Builder pattern?

Answer

A creational pattern that constructs complex objects step by step. Separates object construction from representation, allowing flexible creation with many optional parameters.

Question

What is a fluent interface?

Answer

Method chaining where each method returns `this`, allowing code to read like natural language. Example: builder.name("John").age(30).build()

Question

Where should validation happen in Builder?

Answer

In the build() method after all parameters are set. This allows validating all parameters together before creating the object.

Question

Builder vs Constructor?

Answer

Constructor: all params at once, order matters. Builder: step by step, named, optional params, readable.

Question

When to use Builder pattern?

Answer

When object has many optional parameters, when construction is complex, when you want immutable objects, or when readability matters.

Revision Notes

Key Takeaways

  • 1.Builder solves the telescoping constructor problem
  • 2.Each builder method returns `this` for fluent interface chaining
  • 3.Validation happens in build() after all parameters are set
  • 4.Builders enable immutable object creation
  • 5.Builder is step-by-step; Factory is one-step creation

Interview Tips

  • Show Builder pattern when designing classes with many parameters
  • Explain how validation in build() ensures object integrity
  • Discuss fluent interfaces for readable code
  • Compare Builder vs Factory when discussing creation patterns

Cheat Sheet

Builder Pattern - Cheat Sheet

Problem:
Telescoping constructor with many optional parameters.

Solution:
Step-by-step construction with fluent interface.

Structure:

Product (immutable)
└── Builder (static nested)
    - Required params in constructor
    - Optional params via methods
    - build() validates and creates

Key Points:

  • Each method returns this
  • Validation in build()
  • Creates immutable objects
  • Readable, self-documenting

Builder vs Factory:

Builder Factory
Creation Step by step One step
Complexity Complex objects Any
Control Fine-grained Coarse

Java Examples:
StringBuilder, Stream.Builder, ProcessBuilder