← System Design
🏭
System Design

Factory

Hide which concrete class gets built. The caller asks for a Notifier; the factory decides Email vs SMS vs Push.

TL;DR

A Factory is an object whose only job is to construct other objects. The caller asks for a `Notifier` for `userPrefs`; the factory decides whether to return an `EmailNotifier`, `SmsNotifier`, or `PushNotifier`. The caller never sees `new`. This decouples *what to build* from *which concrete class to build* — letting you add new variants without touching any caller.

When to use

Whenever construction logic is non-trivial, varies by input, or you want to swap concrete implementations without touching the caller. Factories are foundational for plugin architectures, dependency injection, and any system where you might add new types later (payment methods, storage backends, message formats).

Try it

The shape of a factory

interface Notifier { send(msg: string): void; }

class NotifierFactory {
  static create(channel: 'email' | 'sms' | 'push'): Notifier {
    switch (channel) {
      case 'email': return new EmailNotifier();
      case 'sms':   return new SmsNotifier();
      case 'push':  return new PushNotifier();
    }
  }
}

// Caller — no `new` of a concrete class anywhere.
const notifier = NotifierFactory.create(user.preferredChannel);
notifier.send('Order confirmed');

Why this matters in system design

In service architectures, factories show up as transport abstractions and provider abstractions:

  • A StorageClient.create(config) returning S3Storage, AzureBlobStorage, or GcsStorage based on environment.
  • A PaymentGateway.for(merchant) returning the right Stripe / Adyen / Razorpay client.
  • A message-broker abstraction that returns Kafka, RabbitMQ, or in-memory based on config — production uses Kafka, tests use in-memory.

Factory Method vs Abstract Factory

Trade-offs

Pros: Decouples construction from use; supports Open/Closed; centralizes the where-to-add-new-types decision.

Cons: Indirection has a cognitive cost; switch-on-type factories can drift toward god-objects if you keep stuffing variants in. When that happens, split into a registry (config maps type names to constructors) or move to DI.

📺 Video

A YouTube walkthrough — coming once the deep-dive video is published.

🧪 Simulator

An interactive simulator for this concept is on the way — tweak the knobs, watch behaviour change in real time.

💻 Code

A 30-line build challenge with starter code, hints, and a reference implementation.

🎯 Common interview questions
Q1. What's the difference between Factory Method and Abstract Factory?

Factory Method — *one* method that returns one product type, often overridden by subclasses (`createNotifier()`). Abstract Factory — an *interface* that creates a *family* of related products (`UiFactory.createButton()`, `UiFactory.createCheckbox()`). Use Factory Method for variation in one product, Abstract Factory for variation in a whole product family (e.g. dark vs light theme).

Q2. Why not just call `new` directly?

Because every place that says `new EmailNotifier()` is hard-wired to that class. Add SMS support and you have to find every call site. With a factory, the decision lives in one place — change the factory, every caller adapts automatically. This is the Open/Closed principle in concrete form.

Q3. How does this relate to Dependency Injection?

DI containers *are* sophisticated factories. You register `INotifier → EmailNotifier` once, and the container constructs and injects the right concrete type wherever an `INotifier` is requested. The factory is just hidden inside the container's resolver.

Q4. When does a factory become an anti-pattern?

When it adds indirection for a single concrete type that will never have variants. `OrderFactory.createOrder()` that always returns `new Order()` is just `new Order()` with extra steps. Factories pay off when there are *multiple* products or non-trivial construction logic — otherwise it's a YAGNI violation.

Q5. Show me a real-world factory in a famous codebase.

`java.util.Calendar.getInstance()` returns a locale-appropriate calendar. `URL.openConnection()` returns an `HttpURLConnection` or `HttpsURLConnection`. AWS SDK clients — `S3Client.builder().build()` is a builder-flavored factory that picks the right transport. Spring's `@Bean` methods are factories the container invokes for you.

↗ Related concepts

Comments 0

Discuss this page. Markdown supported. Be kind.

Loading…
Loading comments…