image title

Creational Patterns - Extended Explanation with code examples

These patterns are all about how objects are created. Instead of scattering new Something() calls everywhere, creational patterns give you structured ways to handle object creation — making your code more flexible when requirements change. Here is list of well known creational patterns.

These patterns are all about how objects are created. Instead of scattering new Something() calls everywhere, creational patterns give you structured ways to handle object creation — making your code more flexible when requirements change. Here is list of well known creational patterns.

  • Singleton: Ensures a class has only one instance and provides a global point of access.
  • Factory Method: Defines an interface for creating an object, but lets subclasses decide which class to instantiate.
  • Abstract Factory: Creates families of related or dependent objects without specifying their concrete classes.
  • Builder: Separates construction of a complex object from its representation, allowing different representations.
  • Prototype: Creates new objects by cloning an existing object.

Singleton Pattern

The Singleton pattern makes sure there is only one instance of a class in your application, and it provides a global point to access that instance. It’s often used for things like logging, configuration, or database connections — where having multiple instances could cause unnecessary overhead or even break the system. Database connections are a perfect candidate for the Singleton pattern. If every part of your app created a new DB connection, you’d waste resources and potentially overwhelm the database. Instead, you create one connection and reuse it everywhere.

javascript : Simple JavaScript Example
1class Database {
2  constructor() {
3    // Singleton pattern
4    if (Database.instance) {
5      return Database.instance; // Return existing instance
6    }
7
8    this.connection = this.connect();
9    Database.instance = this; // Save instance
10  }
11
12  connect() {
13    console.log('Connecting to database...');
14    return {}; // Imagine this is a real DB connection
15  }
16}
17
18// Usage
19const db1 = new Database();
20const db2 = new Database();
21
22console.log(db1 === db2); // true
23

Pros

  • Single point of truth — ensures consistent data/config across the app.
  • Saves resources — avoids creating the same expensive object multiple times.
  • Easy global access — you don’t need to pass the instance around everywhere.

Cons

  • Hidden dependencies — makes it harder to see what parts of the code depend on it.
  • Harder to test — mocking or replacing the singleton in tests can be tricky.
  • Global state risk — overusing singletons can make debugging unpredictable.

Factory Method

The Factory Method pattern provides a way to create objects without specifying their exact class in the code that uses them. Instead of using new Something() directly, you call a factory method that decides which specific object to create. This makes it easier to change the type of object being created without rewriting large parts of your code.

javascript
1class Notification {
2  send() {}
3}
4
5class EmailNotification extends Notification {
6  send() {
7    console.log("Sending Email...");
8  }
9}
10
11class SMSNotification extends Notification {
12  send() {
13    console.log("Sending SMS...");
14  }
15}
16
17class NotificationFactory {
18  static create(type) {
19    if (type === 'email') return new EmailNotification();
20    if (type === 'sms') return new SMSNotification();
21    throw new Error('Unknown type');
22  }
23}
24
25const notifier = NotificationFactory.create('sms');
26notifier.send(); // "Sending SMS..."
27

Pros

  • Encapsulates creation logic — you don’t spread new calls everywhere.
  • Easier to extend — adding a new type doesn’t require changing existing code much.
  • Promotes loose coupling — code depends on an interface, not a concrete class.

Cons

  • More classes/complexity — can feel over-engineered for simple cases.
  • Extra abstraction — may make code harder to follow if overused.

Abstract Factory Pattern

The Abstract Factory pattern is like a factory of factories. Instead of creating individual objects directly, you use an abstract factory to produce families of related objects without knowing their concrete classes. It’s especially useful when different configurations or environments require a consistent set of related products.

javascript
1// Product Interfaces
2class PaymentProcessor {
3  process(amount) {}
4}
5class RefundProcessor {
6  refund(transactionId) {}
7}
8
9// Stripe Products
10class StripePaymentProcessor extends PaymentProcessor {
11  process(amount) { console.log(`Stripe: Processing payment of $${amount}`); }
12}
13class StripeRefundProcessor extends RefundProcessor {
14  refund(transactionId) { console.log(`Stripe: Refunding transaction ${transactionId}`); }
15}
16
17// PayPal Products
18class PayPalPaymentProcessor extends PaymentProcessor {
19  process(amount) { console.log(`PayPal: Processing payment of $${amount}`); }
20}
21class PayPalRefundProcessor extends RefundProcessor {
22  refund(transactionId) { console.log(`PayPal: Refunding transaction ${transactionId}`); }
23}
24
25// Abstract Factory
26class PaymentGatewayFactory {
27  createPaymentProcessor() {}
28  createRefundProcessor() {}
29}
30
31// Stripe Factory
32class StripeFactory extends PaymentGatewayFactory {
33  createPaymentProcessor() { return new StripePaymentProcessor(); }
34  createRefundProcessor() { return new StripeRefundProcessor(); }
35}
36
37// PayPal Factory
38class PayPalFactory extends PaymentGatewayFactory {
39  createPaymentProcessor() { return new PayPalPaymentProcessor(); }
40  createRefundProcessor() { return new PayPalRefundProcessor(); }
41}
42
43// Usage
44function processOrder(factory) {
45  const paymentProcessor = factory.createPaymentProcessor();
46  const refundProcessor = factory.createRefundProcessor();
47
48  paymentProcessor.process(100);
49  refundProcessor.refund('TX12345');
50}
51
52// Switch between Stripe and PayPal easily
53const gatewayType = 'paypal';
54const factory = gatewayType === 'stripe' ? new StripeFactory() : new PayPalFactory();
55
56processOrder(factory);
57// PayPal: Processing payment of $100
58// PayPal: Refunding transaction TX12345
59

Pros

  • Keeps product families consistent — ensures that related objects are designed to work together.
  • Encapsulates creation logic — client code doesn’t know (or care) which concrete classes are being used.
  • Easy to swap product families — just change the factory, not the whole codebase.

Cons

  • Can be overkill for small projects.
  • More complexity and boilerplate — more interfaces/classes to manage.
  • Can make code harder to navigate if too many layers are involved.

Builder Pattern

The Builder pattern helps you **construct complex objects ** step by step without needing a huge, complicated constructor or multiple overloaded ones. It’s perfect when creating an object requires setting many optional parameters or going through a multi-step process.

Instead of passing 15 arguments into a constructor (and remembering their order), you build the object gradually, calling methods for each part, and then finish with a .build() method.

javascript : Real World Example: Query Builder
1class Query {
2  constructor() {
3    this.table = '';
4    this.fields = [];
5    this.conditions = [];
6    this.order = '';
7  }
8}
9
10class QueryBuilder {
11  constructor() {
12    this.query = new Query();
13  }
14
15  select(fields) {
16    this.query.fields = fields;
17    return this;
18  }
19
20  from(table) {
21    this.query.table = table;
22    return this;
23  }
24
25  where(condition) {
26    this.query.conditions.push(condition);
27    return this;
28  }
29
30  orderBy(order) {
31    this.query.order = order;
32    return this;
33  }
34
35  build() {
36    return this.query;
37  }
38}
39
40// Usage
41const query = new QueryBuilder()
42  .select(['id', 'name', 'email'])
43  .from('users')
44  .where('active = true')
45  .orderBy('name ASC')
46  .build();
47
48console.log(query);
49/*
50Query {
51  table: 'users',
52  fields: [ 'id', 'name', 'email' ],
53  conditions: [ 'active = true' ],
54  order: 'name ASC'
55}
56*/
57

Pros

  • Improves readability — code shows what is being set, not just a long list of parameters.
  • Flexible construction — you can build different variations of the same object.
  • Separates construction from representation — keeps your main class clean.

Cons

  • More boilerplate — you need a separate builder class or logic.
  • Extra complexity — overkill for simple objects.

Real-World Usage Cases

  • SQL/ORM Query Builders — like Knex.js, TypeORM, Sequelize query builders.
  • HTTP Request Builders — e.g., creating complex API requests with headers, query params, and body in stages.
  • Configuration Objects — setting up services with many optional settings (e.g., AWS SDK clients, authentication systems).
  • Document Builders — generating PDFs, emails, or structured reports step-by-step.

Prototype Pattern

The Prototype pattern is about creating new objects by cloning existing ones instead of building them from scratch. This is especially useful when creating an object is expensive (in time or resources) or when the object has a complex configuration that you want to reuse.

Rather than re-running all the setup logic, you copy an existing, preconfigured instance and tweak what you need.

javascript : Cloning Preconfigured API Clients
1class ApiClient {
2  constructor(baseURL, headers = {}) {
3    this.baseURL = baseURL;
4    this.headers = headers;
5  }
6
7  clone() {
8    // Deep copy headers to avoid shared state
9    return new ApiClient(this.baseURL, { ...this.headers });
10  }
11}
12
13// Base client for production
14const prodClient = new ApiClient('https://api.example.com', {
15  Authorization: 'Bearer PROD_TOKEN',
16  'Content-Type': 'application/json'
17});
18
19// Clone and modify for a test environment
20const testClient = prodClient.clone();
21testClient.baseURL = 'https://api.test.example.com';
22testClient.headers.Authorization = 'Bearer TEST_TOKEN';
23
24console.log(prodClient);
25console.log(testClient);
26

Pros

  • Performance boost — avoids expensive re-initialization.
  • Easier duplication — you don’t have to repeat complex setup code.
  • Keeps object structure consistent — clones inherit the original’s properties and behavior.

Cons

  • Shallow vs. deep copy issues — you must be careful to copy nested objects correctly.
  • Risk of unintended shared state if cloning is done incorrectly.
  • Not always obvious where changes should be made after cloning.

Real-World Usage Cases

  • Database connection templates — clone a configured connection and change only the database name.
  • Preconfigured API clients — as in the example, switching environments without redefining everything.
  • Game entities — cloning a base enemy or character with slight variations.
  • Document templates — duplicate a predefined structure and fill in different data.

Random things I built,
develop
and care about

Because spending countless hours debugging and perfecting something no one asked for is definitely my idea of fun. 🤷🏻‍♂️