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
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
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
| Use case | What the factory decides |
|---|---|
| Payment providers | Stripe, Lemon Squeezy or PayPal, based on the customer's country or a config flag. |
| Notification channels | Email, SMS, push or Slack sender, based on the user's preferences. |
| File parsers | JSON, CSV or XML parser, based on the file extension or MIME type. |
| Storage backends | Local disk in development, S3 or Cloudflare R2 in production. |
| Logger transports | Pretty console output locally, structured JSON shipped to a service in production. |
| Document exporters | PDF, CSV or Excel exporter, based on what the user picked. |
| Test doubles | The 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
HTMLElementsubclass. You never writenew 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:
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 Method | Abstract Factory | |
|---|---|---|
| Produces | One product | A family of related products |
| Question it answers | Which class do I build? | Which matching set do I build? |
| Typical size | One function | An 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 returnsnew ConsoleLogger()is a function that renamesnew. 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
newgets called. The mechanism above is the same, syntax aside, whether the language is JavaScript, Python or Java.
Now practice it
1 challenge · 2 quizzesReading this page is the cheap half. These are the exercises that make you use it.
- Factory MethodMove a new call behind a function and prove every caller stays untouched when a new concrete class arrives — the indirection this page builds by hand.ChallengeSolve · EasyChallengeSolve · Easy
- Design Patterns · Creational PatternsTen quick questions across Singleton, Factory Method, Abstract Factory, Builder and Prototype, to check the mental model before this one.QuizTake · EasyQuizTake · Easy
- Design Patterns · Creational Patterns — Series 3ProA second, tougher pass on the same creational family, for once the first set feels easy.QuizTake · EasyQuizTake · Easy
Read next
Abstract Factory pattern
Guaranteeing a whole family of related objects ships together, the extra interface every product carries, and the mismatch when a factory returns the wrong family.
7 min
Singleton pattern
The guarantee that exactly one instance can exist, where it earns its place (database pools, caches, config), the global state it costs, and why it breaks on serverless.
7 min