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
newin 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
// 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.
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 thisThe 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.
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
// 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
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
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
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...
// 24Both shapes have .area(). The forEach loop works without knowing the concrete type behind each object.
Middle: payment processor
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
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 readyA concise answer to help you respond confidently on this topic during an interview.