Skip to main content

What is Factory design pattern?

Factory design pattern - a creational pattern that puts object creation behind a function or method, so calling code never needs to use new directly on concrete classes.

Theory

TL;DR

  • Factory wraps new in one place; callers ask for an object by type, not by class name
  • Useful when the object type depends on runtime data: a config string, a user role, an environment flag
  • Three forms: Simple Factory (one static method), Factory Method (subclass overrides create()), Abstract Factory (family of related objects)
  • Trade-off: Simple Factory requires edits when you add a new type; Factory Method solves that with subclasses

Quick example

javascript
// Without factory - caller knows every concrete class const user = role === 'admin' ? new AdminUser(data) : new RegularUser(data); // With factory - caller only knows the factory const user = UserFactory.create(role, data);

How Simple Factory works

A Simple Factory is a static method (or a plain function) that takes a type string and returns the matching object. No concrete class leaks out to the caller.

javascript
class NotificationFactory { static create(channel, message) { switch (channel) { case 'email': return new EmailNotification(message); case 'sms': return new SMSNotification(message); case 'push': return new PushNotification(message); default: throw new Error(`Unknown channel: ${channel}`); } } } const notif = NotificationFactory.create('email', 'Hello!'); notif.send(); // EmailNotification handles this

The switch lives in one file. Adding a new channel means changing one class, not hunting through the codebase.

Factory Method pattern

Simple Factory is a popular convention but is not in the original Gang of Four book. Factory Method is the actual GoF pattern. A base class defines a method like createProduct(), and each subclass overrides it to return a different object type.

javascript
class Dialog { createButton() { throw new Error('createButton must be implemented'); } render() { const button = this.createButton(); // factory method button.onClick(() => this.closeDialog()); button.render(); } } class WindowsDialog extends Dialog { createButton() { return new WindowsButton(); } } class WebDialog extends Dialog { createButton() { return new HTMLButton(); } }

The render() method in the base class works with any button type. You add a new platform by adding a new subclass, not by editing Dialog.

When to use

  • The type of object to create comes from a config value, a user selection, or an environment variable
  • You want to swap implementations without touching the caller (handy in tests: the factory returns a mock, the real code never changes)
  • Several classes share an interface but differ in behavior
  • You're writing a library and consumers should be able to plug in their own implementations

In practice, the clearest signal is when calling code already has an if/else or switch to pick a class. That logic belongs in a factory, not scattered across features.

Don't add a factory just because you have two classes. If you always create the same type, new is the right call.

Common mistakes

Mistake 1: A factory that never branches

javascript
// Pointless indirection class UserFactory { static create(data) { return new User(data); // always the same class } }

If there is no branching, there is no reason for a factory.

Mistake 2: Objects with inconsistent interfaces

javascript
class AnimalFactory { static create(type) { if (type === 'dog') return new Dog(); // has .bark() if (type === 'cat') return new Cat(); // has .meow() // caller still has to check the type - factory helps nothing } }

All objects from a factory should share the same interface. If Dog and Cat have different APIs, the caller is back to checking types manually.

Mistake 3: No default case

javascript
static create(type) { switch (type) { case 'admin': return new AdminUser(); case 'guest': return new GuestUser(); // no default - returns undefined silently } }

Always throw on an unknown type. Silent undefined causes confusing bugs downstream.

Mistake 4: Treating Simple Factory and Factory Method as the same thing

They are different. Simple Factory is a helpful convention. Factory Method is a formal GoF pattern built on subclass inheritance. In interviews, ask which one the question is about before answering.

Real-world usage

  • React: React.createElement() is a factory function. Pass a string or component class, get back a virtual DOM element.
  • Node.js http: http.createServer() wraps the constructor and returns a Server instance.
  • Express: express() is a factory that returns an Application object.
  • Jest: jest.fn() returns a configured mock function.
  • Angular DI: the injector uses factory functions to create service instances.

Follow-up questions

Q: What is the difference between Simple Factory and Factory Method?
A: Simple Factory is a static method or function that picks a class and calls new. Factory Method is a GoF pattern where a base class defines a method that subclasses override to produce objects. With Simple Factory you edit one file to add a type; with Factory Method you add a new subclass.

Q: How does Factory relate to Abstract Factory?
A: Abstract Factory produces families of related objects. Instead of one factory making one type, you have a factory interface with several creation methods. A WindowsUIFactory creates WindowsButton and WindowsScrollbar together. Use it when objects need to be consistent with each other.

Q: Can you write a factory without a class?
A: Yes. A plain function works fine. function createUser(role, data) { ... } is a factory. The class adds nothing here except a namespace. Many JavaScript codebases prefer plain factory functions over static class methods.

Q: How does Factory help with testing?
A: You inject the factory as a dependency instead of calling new directly in business logic. In tests, you pass a fake factory that returns mocks. The code under test never knows the difference.

Q: When would you pick Factory Method over Simple Factory?
A: When new types appear often, or when external code should be able to define new types without touching your source. Factory Method allows extension without modification.

Examples

Basic: shape creation

javascript
class Circle { constructor(radius) { this.radius = radius; } area() { return Math.PI * this.radius ** 2; } } class Rectangle { constructor(w, h) { this.width = w; this.height = h; } area() { return this.width * this.height; } } function createShape(type, ...args) { if (type === 'circle') return new Circle(...args); if (type === 'rectangle') return new Rectangle(...args); throw new Error(`Unknown shape: ${type}`); } const shapes = [ createShape('circle', 5), createShape('rectangle', 4, 6), ]; shapes.forEach(s => console.log(s.area())); // 78.53... // 24

Both shapes have .area(). The forEach loop works without knowing the concrete type behind each object.

Middle: payment processor

javascript
class StripeProcessor { pay(amount) { console.log(`Stripe: charging $${amount}`); } } class PayPalProcessor { pay(amount) { console.log(`PayPal: sending $${amount}`); } } class CryptoProcessor { pay(amount) { console.log(`Crypto: transferring $${amount} in BTC`); } } class PaymentFactory { static create(method) { const map = { stripe: StripeProcessor, paypal: PayPalProcessor, crypto: CryptoProcessor, }; const Processor = map[method]; if (!Processor) throw new Error(`Unsupported method: ${method}`); return new Processor(); } } // method comes from user selection at checkout const processor = PaymentFactory.create(getUserSelectedMethod()); processor.pay(99.99);

The checkout flow imports one factory, not three processor classes. Replacing Stripe with another provider means one line changes inside PaymentFactory, nothing else.

Senior: factory with self-registration

javascript
class PluginFactory { static #registry = new Map(); static register(name, Constructor) { this.#registry.set(name, Constructor); } static create(name, ...args) { const Constructor = this.#registry.get(name); if (!Constructor) throw new Error(`Plugin "${name}" not registered`); return new Constructor(...args); } } // Core plugins register on startup PluginFactory.register('logger', LoggerPlugin); PluginFactory.register('cache', CachePlugin); // A third-party plugin extends the system without touching PluginFactory PluginFactory.register('analytics', AnalyticsPlugin); const logger = PluginFactory.create('logger', { level: 'debug' });

No switch statement to edit. New types register themselves. This pattern appears in Webpack loaders, Babel plugins, and similar extensible systems.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?