codejump

AcademyDesign Patterns

Factory Method pattern

Hiding which concrete class gets built behind one function, the indirection it costs when the choice might never grow, and the leak when a return type still names the class.

7 min readUpdated

3 exercises

Waiting at the end of this page.

Factory Method answers a different question from Singleton's: not how many instances exist, but which concrete class gets built when the caller doesn't, or shouldn't, know.

A Factory Method moves the new call behind a function, so calling code asks for "a shape" instead of committing to Circle or Square by name. The caller depends on the returned interface, never on which constructor ran.

The problem it solves

Your checkout has to charge a card. Today it uses one payment provider, so somewhere in the code there is a new StripeProvider(). Then the business asks for a second provider for US customers, and a third for a market where the first two are unavailable.

Without a factory, that new is copied into every place that takes a payment: checkout, refunds, subscription renewal, the admin panel. Each copy grows an if (country === 'EU') ... else if ... chain.

  • Adding a provider means editing every one of those places, and missing one is a production bug.
  • Each call site is now coupled to three concrete classes it does not care about.
  • Tests cannot swap in a fake provider without editing the code under test.

The fix is to have exactly one function that knows how to pick and build a provider. Everything else asks it for "a payment provider" and only ever uses the interface.

Structure

The client asks the factory for a Shape and only ever depends on the Shape interface, never on Circle or Square
The client asks the factory for a Shape and only ever depends on the Shape interface, never on Circle or Square

Three parts:

  • A product interface (Shape) that every concrete class implements.
  • Concrete products (Circle, Square), each hiding its own construction details.
  • A factory method (createShape) that decides which concrete product to build and returns it as the interface.
class Circle {
  area() { return 'circle-area'; }
}
class Square {
  area() { return 'square-area'; }
}

function createShape(kind) {
  if (kind === 'circle') return new Circle();
  if (kind === 'square') return new Square();
  throw new Error('Unknown shape: ' + kind);
}

The payoff shows up the day a fourth shape arrives. Every caller of createShape stays untouched, because none of them ever wrote Triangle by name. Only createShape needs to change. That is the point of the indirection: it turns "every call site" into "one function" as the set of concrete classes changes underneath it.

Where it is used in real systems

Checkout asks one factory for a payment provider. Adding a provider changes the factory, not the callers
Checkout asks one factory for a payment provider. Adding a provider changes the factory, not the callers
Use caseWhat the factory decides
Payment providersStripe, Lemon Squeezy or PayPal, based on the customer's country or a config flag.
Notification channelsEmail, SMS, push or Slack sender, based on the user's preferences.
File parsersJSON, CSV or XML parser, based on the file extension or MIME type.
Storage backendsLocal disk in development, S3 or Cloudflare R2 in production.
Logger transportsPretty console output locally, structured JSON shipped to a service in production.
Document exportersPDF, CSV or Excel exporter, based on what the user picked.
Test doublesThe real client in production, a fake one in tests, chosen in one place.

In every row the caller wants "something that does X", and the decision of which implementation is a separate concern that changes for its own reasons: a new market, a new file format, a new environment.

Factory Methods you already use

  • `document.createElement('div')` takes a string and returns the right HTMLElement subclass. You never write new HTMLDivElement().
  • `React.createElement(type, props)` builds an element from a component or a tag name chosen at runtime.
  • `crypto.createHash('sha256')` returns the right hash implementation for the algorithm you named.
  • `Array.from(...)`, `Promise.resolve(...)`, `Object.create(...)` are factory functions, each hiding how the object is actually produced.
  • NestJS custom providers with `useFactory` let the framework call your function to build a dependency, often reading config to decide which class to return.

Beyond the if/else chain

The if (kind === ...) chain works, but it has to be edited for every new class. A lookup table keeps the factory closed to modification, so adding a case means adding one line:

TypeScript
type Channel = 'email' | 'sms' | 'push';

const senders: Record<Channel, () => Sender> = {
  email: () => new EmailSender(),
  sms: () => new SmsSender(),
  push: () => new PushSender(),
};

function createSender(channel: Channel): Sender {
  return senders[channel]();
}

Because the keys are a union type, the compiler also refuses a Channel value that has no entry in the table.

A note on vocabulary. In the original Gang of Four book, Factory Method means a method that subclasses override to decide what to build. In JavaScript and TypeScript you will almost always see the simpler form shown above, a plain function or a lookup table, often called a "simple factory". The idea is the same: creation is hidden behind one place. Say which one you mean in an interview.

Factory Method or Abstract Factory

Factory MethodAbstract Factory
ProducesOne productA family of related products
Question it answersWhich class do I build?Which matching set do I build?
Typical sizeOne functionAn interface with several creation methods

If you only need one kind of object, Factory Method is enough. Reach for Abstract Factory when several objects must always come from the same family.

When not to use it

A factory is one more indirection, and it only pays for itself when the choice can actually vary.

  • Only one concrete class, and no second one in sight. A createLogger() that always returns new ConsoleLogger() is a function that renames new. Add the factory the day a second implementation appears.
  • A factory per class by reflex. If every class gets its own factory, you have doubled the number of files and made the code harder to follow.
  • A factory that grows business logic. It should pick and build. Once it validates, logs and retries, it has become a service and needs a different name and a different test.

What interviewers ask

  • "What is the difference between Factory Method and Abstract Factory?" One product versus a family of products that must match.
  • "Why not just call the constructor?" Callers would depend on concrete classes, so adding one means editing every caller. The factory concentrates that change in one place.
  • "How do you avoid a giant switch statement?" A lookup table keyed by a union type, or a registry that classes add themselves to.
  • "Give a real example." Payment providers, storage backends, or document.createElement.
  • "How does it help testing?" Tests can inject a factory that returns fakes, so the code under test never touches the real provider.

The mistake to avoid

A Factory Method that still leaks the concrete class through its return type. If createShape returns Circle instead of Shape, callers can quietly start depending on Circle-only members, and the indirection stops protecting anything. Always declare the return type as the interface.

Factory Method is one naming device among several an object-oriented language gives you for controlling how new gets called. The mechanism above is the same, syntax aside, whether the language is JavaScript, Python or Java.

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.