Skip to contentJMRG
All posts

Builder Pattern: Step-by-Step Construction of Complex Objects

design-patternscreationalbuilder

Intent

The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It's especially useful when an object has many optional parameters.

The Problem

Have you seen constructors like this?

// Telescopic constructor - hard to use and maintain
const user = new User(
  'John',
  'Doe',
  '[email protected]',
  true,
  false,
  'premium',
  null,
  'dark',
  ['es', 'en']
);

Without looking at documentation, what does each parameter mean? What if you only want to set the theme without modifying the plan?

The Solution: Builder

const user = new UserBuilder()
  .withName('John', 'Doe')
  .withEmail('[email protected]')
  .asVerified()
  .withPlan('premium')
  .withTheme('dark')
  .withLanguages(['es', 'en'])
  .build();

Each method is self-documenting. You only set what you need.

Diagram

classDiagram
    class Builder {
        <<interface>>
        +setPartA()
        +setPartB()
        +build(): Product
    }

    class ConcreteBuilder {
        -product: Product
        +setPartA()
        +setPartB()
        +build(): Product
    }

    class Director {
        -builder: Builder
        +construct()
    }

    class Product {
        +partA
        +partB
    }

    Builder <|.. ConcreteBuilder
    Director --> Builder
    ConcreteBuilder --> Product

TypeScript Implementation

// Product: HTTP Request
interface HttpRequest {
  method: string;
  url: string;
  headers: Record<string, string>;
  body?: unknown;
  timeout: number;
  retries: number;
}

// Builder with fluent API
class HttpRequestBuilder {
  private request: Partial<HttpRequest> = {
    headers: {},
    timeout: 30000,
    retries: 0,
  };

  get(url: string): this {
    this.request.method = 'GET';
    this.request.url = url;
    return this;
  }

  post(url: string): this {
    this.request.method = 'POST';
    this.request.url = url;
    return this;
  }

  withHeader(key: string, value: string): this {
    this.request.headers![key] = value;
    return this;
  }

  withAuth(token: string): this {
    return this.withHeader('Authorization', `Bearer ${token}`);
  }

  withBody(body: unknown): this {
    this.request.body = body;
    return this.withHeader('Content-Type', 'application/json');
  }

  build(): HttpRequest {
    if (!this.request.method || !this.request.url) {
      throw new Error('Method and URL are required');
    }
    return this.request as HttpRequest;
  }
}

When to Use

Situation Recommendation
Object with many optional parameters ✅ Recommended
Complex configurations ✅ Recommended
Step-by-step construction with validation ✅ Recommended
Simple object with few parameters ❌ Overkill

Conclusion

The Builder pattern is your best ally for building complex objects in a readable and maintainable way.