# Creational Patterns

Course: [Low-Level Design](https://www.devtutorials.ai/learn/system-design/lld) · Section: LLD Foundations
Source: https://www.devtutorials.ai/learn/system-design/lld/foundations/creational-patterns

> Learn simple factory, factory method, builder and singleton with QuickBite examples, then replace an if-chain with a notifier factory and add WhatsApp.

Think about how a restaurant makes a dosa. The waiter does not go into the kitchen and cook it. Instead, they write "1 masala dosa" on an order slip and give it to the kitchen. The kitchen decides who will prepare it and how it will be made. The waiter simply receives the finished dosa.

So far, our code has often worked like a waiter who also cooks the food. Every place that needs an object directly creates it using `new Card()` or `new SmsNotifier()`. When the implementation changes, every place that creates those objects must also be updated.

**Creational patterns** provide common and proven ways to create objects, so the rest of the code does not need to know how those objects are built.

**In simple words:** creational patterns put the "which object should be created, and how should it be built" decision in one central place.

## What You Will Learn

In this lesson, you will learn:

1. Why creating objects with `new` everywhere can cause problems.
2. The **simple factory**: choose a class from one central location.
3. The **factory method**: allow a subclass to decide which object to create.
4. The **builder**: create a complex object step by step.
5. The **singleton**: use one shared object across the application, and understand when it should not be used.

A **design pattern** is a named solution to a problem that keeps coming up. The names come from the 1994 "Gang of Four" book, and they let a team say "use a builder here" instead of explaining the whole solution.

## The Problem With `new` Everywhere

Here is a payment choice written as an if-chain: `if card, new Card(); else if upi, new Upi(); else if cash, new Cash()`. It works: `upi` with 300 gives "upi 300", and `cash` with 120 gives "cash 120". But QuickBite has the same if-chain in checkout, in refunds and on the admin page. Watch what happens when a new payment type arrives:

The code behind the animation:

```ts runnable title="The problem: choosing a class with an if-chain"
class Card { pay(n: number): string { return `card ${n}`; } }
class Upi { pay(n: number): string { return `upi ${n}`; } }
class Cash { pay(n: number): string { return `cash ${n}`; } }

// This choice is copied into checkout, refunds and the admin page.
function chargeAtCheckout(method: string, amount: number): string {
  if (method === "card") return new Card().pay(amount);
  else if (method === "upi") return new Upi().pay(amount);
  else if (method === "cash") return new Cash().pay(amount);
  throw new Error("unknown method");
}

console.log(chargeAtCheckout("upi", 300));
console.log(chargeAtCheckout("cash", 120));
```

Expected output:

```text
upi 300
cash 120
```

### Why It Hurts

When QuickBite adds a wallet payment option, developers must find and update all three `if` chains. If even one of them is missed, wallet payments may work in some places but fail in others, such as during refunds.

The solution is to make the object creation decision in a single place. This central place is called a **factory**.

## The Simple Factory

A factory works like a restaurant kitchen. You place an order by name, and the kitchen gives you the finished dish. You do not need to know who prepared it or how it was made.

### The Example

The factory keeps a table from name to "how to make it", called `MAKERS`. Callers only ever call `createPayment(name)`. Step through three calls:

Here is the same idea in code:

```ts runnable title="A simple factory: one table, one place"
interface PaymentMethod {
  pay(amount: number): string;
}
class Card implements PaymentMethod { pay(n: number): string { return `card ${n}`; } }
class Upi implements PaymentMethod { pay(n: number): string { return `upi ${n}`; } }
class Cash implements PaymentMethod { pay(n: number): string { return `cash ${n}`; } }

const MAKERS: Record<string, () => PaymentMethod> = {
  card: () => new Card(),
  upi: () => new Upi(),
  cash: () => new Cash(),
};

function createPayment(name: string): PaymentMethod {
  const make = MAKERS[name];
  if (!make) throw new Error("unknown payment method: " + name);
  return make();
}

console.log(createPayment("upi").pay(300));
console.log(createPayment("card").pay(450));
try {
  createPayment("cheque");
} catch (e) {
  console.log((e as Error).message);
}
```

Expected output:

```text
upi 300
card 450
unknown payment method: cheque
```

| Call | Result |
|---|---|
| `createPayment("upi").pay(300)` | upi 300 |
| `createPayment("card").pay(450)` | card 450 |
| `createPayment("cheque")` | error: unknown payment method: cheque |

### Why It Helps

To add a wallet payment method, you only need to create one new class and add one entry to the factory. The checkout system, refund system, and admin page can all use it automatically without any additional changes.

If an unknown payment name is provided, the factory can produce one clear error in a single place. Without a factory, every `if` chain would need to remember to handle unknown values.

## The Factory Method

Sometimes the overall process stays the same, but one part of it changes. For example, QuickBite may use bike delivery in the city and drone delivery on a college campus. The order process is the same in both cases. Only the delivery method is different.

The **factory method** pattern keeps the shared process in a parent class. The parent class leaves one method undefined: the method responsible for creating the part that changes. Each child class provides its own implementation of that method.

### The Example

`OrderFlow` is the parent class. Its `place(dish)` method holds the shared steps. It calls `createDelivery()`, which each child class fills in.

<LldDiagram caption="The factory method createDelivery() is filled in by each subclass" code={"classDiagram\n  class OrderFlow {\n    <<abstract>>\n    #createDelivery() Delivery\n    +place(dish) string\n  }\n  class CityFlow {\n    #createDelivery() Delivery\n  }\n  class CampusFlow {\n    #createDelivery() Delivery\n  }\n  class Delivery {\n    <<interface>>\n    +eta() string\n  }\n  class BikeDelivery\n  class DroneDelivery\n  OrderFlow <|-- CityFlow\n  OrderFlow <|-- CampusFlow\n  Delivery <|.. BikeDelivery\n  Delivery <|.. DroneDelivery\n  CityFlow ..> BikeDelivery : creates\n  CampusFlow ..> DroneDelivery : creates"} />

Now step through two orders, one in the city and one on campus:

The code behind the animation:

```ts runnable title="Factory method: the subclass decides what to create"
interface Delivery {
  eta(): string;
}
class BikeDelivery implements Delivery { eta(): string { return "30 min by bike"; } }
class DroneDelivery implements Delivery { eta(): string { return "12 min by drone"; } }

abstract class OrderFlow {
  // The factory method. Each subclass fills it in.
  protected abstract createDelivery(): Delivery;

  // The shared steps never change.
  place(dish: string): string {
    const delivery = this.createDelivery();
    return `${dish}: ${delivery.eta()}`;
  }
}

class CityFlow extends OrderFlow {
  protected createDelivery(): Delivery { return new BikeDelivery(); }
}
class CampusFlow extends OrderFlow {
  protected createDelivery(): Delivery { return new DroneDelivery(); }
}

console.log(new CityFlow().place("Biryani"));
console.log(new CampusFlow().place("Sandwich"));
```

Expected output:

```text
Biryani: 30 min by bike
Sandwich: 12 min by drone
```

The results are "Biryani: 30 min by bike" and "Sandwich: 12 min by drone". `place()` is written only once inside `OrderFlow`. It never directly creates a `BikeDelivery` object with `new BikeDelivery()`. Instead, it calls `createDelivery()` and lets the subclass decide which delivery type to create.

**Easy way to tell them apart:** a **simple factory** is a separate function that creates objects from an input value. A **factory method** is a method a subclass overrides to choose the object. Both keep `new` out of the calling code.

## The Builder

Some objects contain many pieces of information. For example, an order may include a restaurant, several items, a coupon, a delivery note, and other details. A constructor with many parameters can be difficult to read and understand. It is often unclear what each value represents.

A **builder** works like filling out an order form. You provide one piece of information at a time, in any order you choose. The actual object is not created until you select "place order" by calling the build method. At that point, the builder validates the information and creates the final object.

### The Example

Each builder method sets one part and returns the builder (`this`), so you can chain the calls: `new OrderBuilder().from("Saravana").add("Idli", 2).add("Vada").withNote("less spicy").build()`. Step through that chain:

Here is the same idea in code:

```ts runnable title="A builder: one step at a time, checked at the end"
class Order {
  constructor(
    readonly restaurant: string,
    readonly items: { name: string; qty: number }[],
    readonly coupon: string,
    readonly note: string,
  ) {}
}

class OrderBuilder {
  private restaurant = "";
  private items: { name: string; qty: number }[] = [];
  private coupon = "";
  private note = "";

  from(restaurant: string): this { this.restaurant = restaurant; return this; }
  add(name: string, qty = 1): this { this.items.push({ name, qty }); return this; }
  withCoupon(code: string): this { this.coupon = code; return this; }
  withNote(text: string): this { this.note = text; return this; }

  build(): Order {
    if (!this.restaurant) throw new Error("pick a restaurant first");
    if (this.items.length === 0) throw new Error("add at least one item");
    return new Order(this.restaurant, this.items, this.coupon, this.note);
  }
}

const order = new OrderBuilder()
  .from("Saravana")
  .add("Idli", 2)
  .add("Vada")
  .withNote("less spicy")
  .build();

console.log(order.restaurant, order.items.length, order.note);
```

Expected output:

```text
Saravana 2 less spicy
```

The finished order is from Saravana, has 2 items and the note "less spicy". The chain reads like a sentence. You can skip the parts you do not need, like the coupon here.

### Checking at the End

`build()` is the one place that checks the order is complete. An unfinished order is refused with a clear message. Watch three attempts at a Paradise biryani order:

The code behind the animation:

```ts runnable title="The builder refuses an unfinished order"
class OrderBuilder {
  private restaurant = "";
  private items: string[] = [];

  from(restaurant: string): this { this.restaurant = restaurant; return this; }
  add(name: string): this { this.items.push(name); return this; }

  build(): string {
    if (!this.restaurant) throw new Error("pick a restaurant first");
    if (this.items.length === 0) throw new Error("add at least one item");
    return `${this.items.length} items from ${this.restaurant}`;
  }
}

const attempts = [
  () => new OrderBuilder().from("Paradise").add("Biryani").build(),
  () => new OrderBuilder().add("Biryani").build(),
  () => new OrderBuilder().from("Paradise").build(),
];

for (const attempt of attempts) {
  try {
    console.log("ok:", attempt());
  } catch (e) {
    console.log("refused:", (e as Error).message);
  }
}
```

Expected output:

```text
ok: 1 items from Paradise
refused: pick a restaurant first
refused: add at least one item
```

| Attempt | Result |
|---|---|
| `.from("Paradise").add("Biryani").build()` | ok: 1 items from Paradise |
| `.add("Biryani").build()` | refused: pick a restaurant first |
| `.from("Paradise").build()` | refused: add at least one item |

Without a builder, every place that creates an order would need to remember and perform all of these validation checks on its own.

## The Singleton

Some objects should exist only once in an application. For example, QuickBite may have a single set of application settings, such as the delivery fee. Having multiple copies with different values could cause serious problems.

A **singleton** is a class that allows only one instance to be created. Whenever code asks for that object, it receives the same shared instance.

### The Example

The constructor is `private`, so no one can write `new Settings()`. The only way in is `Settings.get()`. Step through two calls to it:

Here is the same idea in code:

```ts runnable title="A singleton: one object for the whole app"
class Settings {
  private static only: Settings | null = null;
  readonly deliveryFee = 30;

  // private: no one outside can write new Settings()
  private constructor() {
    console.log("settings loaded");
  }

  static get(): Settings {
    if (Settings.only === null) Settings.only = new Settings();
    return Settings.only;
  }
}

const a = Settings.get();
const b = Settings.get();
console.log(a === b);
console.log(b.deliveryFee);
```

Expected output:

```text
settings loaded
true
30
```

"settings loaded" is printed only once. Both `a` and `b` refer to the same object, so `a === b` returns true, and `b.deliveryFee` is 30.

### Be Careful With Singletons

A singleton is easy to access from anywhere in the application. However, that convenience can also create problems.

- Any class can start depending on it without making that dependency clear. You cannot see this dependency in the constructor.
- Tests share the same singleton instance. If one test changes its state, the next test may see those changes.

Before using a singleton, try this: create one `Settings` object at startup and pass it to the classes that need it. You still share one object, but each dependency is visible and tests can pass their own.

## Lab: A Notifier Factory

QuickBite supports SMS, email, and push notifications. Currently, the notification type is selected using an if-chain inside `NotificationService`. Now QuickBite wants to support WhatsApp as well.

Keep `createNotifier(channel, log)` and `notify(channel, to, text, log)` unchanged because the tests depend on them.

1. Create a `Notifier` interface with a `send(to, text)` method, and create one class for each notification channel.
2. Convert `createNotifier` into a factory. Look up the channel in a table and throw an error for unknown channels.
3. Add WhatsApp support. This should require only one new class and one new entry in the table. The message should begin with `WHATSAPP to`.

The lab is complete when all tests pass and the if-chain has been removed.

## Predict the Output

Here is a singleton question in C#. Read the code, then type what it prints.

<CsPredict id="singleton-1" code={"var a = Counter.Instance;\nvar b = Counter.Instance;\na.Next();\nb.Next();\nConsole.WriteLine(a.Value);\nConsole.WriteLine(ReferenceEquals(a, b));\n\nclass Counter\n{\n    public static Counter Instance { get; } = new Counter();\n    private Counter() { }\n    public int Value { get; private set; }\n    public void Next() => Value++;\n}"} expected={"2\nTrue"} why="There is only one Counter. a and b are the same object, so both calls to Next() add to the same Value, which ends at 2. ReferenceEquals is True." />

## Common Mistakes

| Mistake | Wrong | Correct | Why it happens |
|---|---|---|---|
| Repeating the same creation logic | An if-chain based on the payment type appears in both checkout and refund code. | Use a single factory and reuse it everywhere. | Copying an existing if-chain often feels like the fastest solution. |
| Hiding errors inside the factory | Returning `null` when an unknown name is provided. | Throw a clear and meaningful error from the factory. | Returning `null` may seem safe until the caller tries to use it. |
| Builder without validation | `build()` creates an order even when it has no items. | `build()` should throw an error when a required value is missing. | Validation can feel like extra work during development. |
| Constructors with too many parameters | `new Order("Saravana", items, "", "", 0, false, "")` | Use a builder with clearly named steps. | New fields are often added one after another to the constructor. |
| Using singletons everywhere | Every service is implemented as a singleton. | Create one shared object at startup and pass it where needed. | Singletons are easy to access from any part of the application. |

## Practice

## What to Remember

- Creational patterns move the decision of **which object to create and how to create it** into one central place.
- **Simple Factory:** a name or type goes in, and a ready-to-use object comes out.
- **Factory Method:** the parent class defines the process, while a subclass decides what object to create.
- **Builder:** create an object one step at a time, then validate everything in `build()`.
- **Singleton:** ensures there is only one instance of an object for the entire application. Use it carefully, and prefer passing a shared object through constructors when possible.

## Further Reading

These are the sources used to check this lesson:

- [Refactoring.Guru: Factory Method](https://refactoring.guru/design-patterns/factory-method): the factory method pattern with diagrams.
- [Refactoring.Guru: Builder](https://refactoring.guru/design-patterns/builder): step-by-step construction.
- [Refactoring.Guru: Singleton](https://refactoring.guru/design-patterns/singleton): the pattern and its problems.
- [TypeScript Handbook: Classes](https://www.typescriptlang.org/docs/handbook/2/classes.html): private constructors, static members and abstract classes.

Next: **Structural Patterns**. You will learn how to fit classes together: adapters, decorators and facades.
