Skip to content

Builder

Builder separates the construction of a complex object from its representation, letting you assemble it through a sequence of clear steps and produce the finished product only at the end.

Some objects have many optional parts. An HTTP request has a method, a URL, optional headers, an optional body, a timeout. Encoding every combination as a constructor leads to either a telescoping list of overloads or one giant constructor full of nulls and booleans whose meaning you cannot read at the call site. new Request("GET", url, null, null, 30, true, false) is a puzzle, not code.

Builder replaces that with named, chainable steps. You start a builder, call only the steps you care about in any order, and finish with a single build() that returns the immutable, validated product. The call site reads like a description of what you want. Construction logic — defaults, validation, assembly — lives in the builder, away from the product’s own responsibilities.

classDiagram
  class HttpRequest {
    +method: string
    +url: string
    +headers: Map
    +body: string
    +summary() string
  }
  class RequestBuilder {
    -method: string
    -url: string
    -headers: Map
    -body: string
    +setMethod(m) RequestBuilder
    +addHeader(k, v) RequestBuilder
    +setBody(b) RequestBuilder
    +build() HttpRequest
  }
  RequestBuilder ..> HttpRequest : builds
A builder accumulates parts and emits the finished request
  • Product (HttpRequest) — the complex object being assembled. It is typically immutable once built.
  • Builder (RequestBuilder) — exposes one step per part, each returning the builder so calls chain, plus a terminal build() that validates and returns the product.
  • Client — calls the steps it needs in any order, then build().

A Director (a class that drives a fixed sequence of builder calls) is an optional extra; for fluent builders like this one the client itself plays that role.

A fluent RequestBuilder that produces an immutable HttpRequest.

class HttpRequest {
constructor(
readonly method: string,
readonly url: string,
readonly headers: Record<string, string>,
readonly body: string | null,
) {}
summary(): string {
const headerCount = Object.keys(this.headers).length;
return `${this.method} ${this.url} (${headerCount} headers, body: ${this.body !== null})`;
}
}
class RequestBuilder {
private method = 'GET';
private headers: Record<string, string> = {};
private body: string | null = null;
constructor(private readonly url: string) {}
setMethod(method: string): this {
this.method = method;
return this;
}
addHeader(key: string, value: string): this {
this.headers[key] = value;
return this;
}
setBody(body: string): this {
this.body = body;
return this;
}
build(): HttpRequest {
return new HttpRequest(this.method, this.url, this.headers, this.body);
}
}
const request = new RequestBuilder('https://api.example.com/items')
.setMethod('POST')
.addHeader('Content-Type', 'application/json')
.setBody('{"name":"book"}')
.build();
console.log(request.summary());
  • Pro: call sites read clearly — each step is named, so the meaning of every value is obvious.
  • Pro: handles many optional parts without a combinatorial explosion of constructors.
  • Pro: validation and defaults live in one place, and the finished product can be immutable.
  • Con: more code than a constructor; only pays off when the object is genuinely complex.
  • Con: a half-built builder is in an incomplete state, so build() should validate required fields.
  • Abstract Factory returns a product in one call, whereas Builder constructs it across several steps.
  • Prototype is an alternative when a configured object already exists and you want a copy.
What problem does Builder mainly address?
Why do builder step methods usually return the builder itself?
What is the role of the terminal build() method?