Builder Pattern
Separates the construction of a complex object from its representation, allowing the same construction process to create different representations. Builder lets you produce different types and representations of an object using the same step-by-step construction code.
✦ Problem
Complex objects can require exhaustive, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried inside a monstrous constructor with lots of parameters, or scattered all over the client code. Creating subclasses for each configuration explodes the number of classes.
✦ Solution
Extract the object construction code out of its own class and move it to separate objects called builders. The pattern organizes object construction into a set of steps. To create an object, you execute a series of these steps on a builder. You don't need to call all steps—only those required for producing a particular configuration. A Director class can define the order of building steps while the builder provides the implementation.
Participants
| Role | Responsibility |
|---|---|
| Builder | Specifies an abstract interface for creating parts of a Product |
| ConcreteBuilder | Constructs and assembles parts of the product by implementing the Builder interface |
| Director | Constructs an object using the Builder interface, defining the sequence of building steps |
| Product | Represents the complex object under construction |
TypeScript Example
class QueryBuilder {
private table = "";
private conditions: string[] = [];
private columns: string[] = ["*"];
private ordering = "";
private limitVal?: number;
from(table: string): this {
this.table = table;
return this;
}
select(...cols: string[]): this {
this.columns = cols;
return this;
}
where(condition: string): this {
this.conditions.push(condition);
return this;
}
orderBy(col: string, dir: "ASC" | "DESC" = "ASC"): this {
this.ordering = `ORDER BY ${col} ${dir}`;
return this;
}
limit(n: number): this {
this.limitVal = n;
return this;
}
build(): string {
const cols = this.columns.join(", ");
let sql = `SELECT ${cols} FROM ${this.table}`;
if (this.conditions.length)
sql += ` WHERE ${this.conditions.join(" AND ")}`;
if (this.ordering) sql += ` ${this.ordering}`;
if (this.limitVal) sql += ` LIMIT ${this.limitVal}`;
return sql;
}
}
const query = new QueryBuilder()
.from("users")
.select("id", "name", "email")
.where("active = true")
.where("role = 'admin'")
.orderBy("name")
.limit(10)
.build();
console.log(query);
// SELECT id, name, email FROM users WHERE active = true AND role = 'admin' ORDER BY name ASC LIMIT 10Real-World Example
Query builders in ORMs like Knex.js, TypeORM's QueryBuilder, or Prisma's fluent API all use the Builder pattern. Instead of writing raw SQL strings, you chain method calls that each configure a part of the query. This approach is also used in HTTP request builders, UI component constructors, and configuration objects where the final product has many optional parameters.
✓ When to Use
- •An object has many optional configuration parameters
- •Construction of different representations of the product involves similar steps that differ only in details
- •You want to make the code more readable by avoiding telescoping constructors
- •You need to construct composite trees or other complex objects step by step
✗ When Not to Use
- •The object is simple with few required fields and no optional ones
- •All configurations of the product are known upfront and unlikely to change
- •The overhead of a separate Builder class is not justified by the complexity of the object
Understanding the Builder Pattern
The Builder pattern is one of the most important creational design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.
Separates the construction of a complex object from its representation, allowing the same construction process to create different representations. Builder lets you produce different types and representations of an object using the same step-by-step construction code. The pattern involves the following key participants: Builder, ConcreteBuilder, Director, Product. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.
In TypeScript and modern JavaScript development, the Builder pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Builder implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.
Knowing when to apply the Builder pattern is as important as knowing how. Common use cases include: An object has many optional configuration parameters; Construction of different representations of the product involves similar steps that differ only in details; You want to make the code more readable by avoiding telescoping constructors. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.
The Builder pattern is related to several other design patterns: Abstract Factory, Factory Method, Singleton, Composite. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.
Related Patterns
Provides an interface for creating families of related or dependent objects without specifying their concrete classes. A…
Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets …
Ensures a class has only one instance and provides a global point of access to it. The Singleton pattern restricts insta…
Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objec…
More Creational Patterns
Explore All Design Patterns
Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.