The Factory Pattern centralizes object creation behind a function, decoupling calling code from the specific details (or specific class) of what gets constructed — especially valuable when the exact type of object needed depends on runtime conditions.
1The Factory Pattern | JavaScript Tutorial - In-Depth Guide Part 1
A factory function creates and returns an object without requiring the caller to use 'new' or know the object's exact construction details.
function createUser(name, role) {
return {
name,
role,
greet() { return `Hi, I'm ${name}`; },
};
}
const user = createUser('Ana', 'admin');Object Creation, Decoupled
2The Factory Pattern | JavaScript Tutorial - In-Depth Guide Part 2
A factory's real power shows up when the exact TYPE of object to create depends on a runtime condition — the caller doesn't need to know or choose the specific class themselves.
function createShape(type, size) {
switch (type) {
case 'circle': return new Circle(size);
case 'square': return new Square(size);
default: throw new Error(`Unknown shape: ${type}`);
}
}Choosing the Type at Runtime
3The Factory Pattern | JavaScript Tutorial - In-Depth Guide Part 3
Factories are also useful for applying consistent defaults, validation, or setup logic every time a certain kind of object is created — logic you don't want repeated at every call site.
function createApiClient(config) {
return {
baseUrl: config.baseUrl ?? 'https://api.example.com',
timeout: config.timeout ?? 5000,
headers: { 'Content-Type': 'application/json', ...config.headers },
};
}Consistent Defaults and Setup
4The Factory Pattern | JavaScript Tutorial - In-Depth Guide Part 4
Unlike a class constructor called with 'new', a factory function is just a regular function — it can conditionally return different shapes of object, or even reuse/cache an existing instance, with no special syntax.
function createLogger(env) {
if (env === 'test') return createNoOpLogger(); // silent during tests
return createRealLogger();
}More Flexible Than a Constructor
5The Factory Pattern | JavaScript Tutorial - In-Depth Guide Part 5
Factories are commonly used to create plain objects rather than class instances, which sidesteps 'this' binding concerns entirely, since there's no 'this' context to worry about losing.
function createCounter() {
let count = 0;
return {
increment: () => ++count, // no 'this' needed at all
};
}
const { increment } = createCounter();
increment(); // works correctly even detached from its objectAvoiding this-Binding Issues
6Step-by-Step Breakdown
A factory function creates and returns an object without requiring the caller to use 'new' or know the object's exact construction details.
A factory's real power shows up when the exact TYPE of object to create depends on a runtime condition — the caller doesn't need to know or choose the specific class themselves.
Checkpoint: Does calling code using a factory function need to know which specific class/shape of object it will receive?
- →Yes, it must specify the exact class to construct
- →No, the factory decides internally based on its logic
Factories are also useful for applying consistent defaults, validation, or setup logic every time a certain kind of object is created — logic you don't want repeated at every call site.
Unlike a class constructor called with 'new', a factory function is just a regular function — it can conditionally return different shapes of object, or even reuse/cache an existing instance, with no special syntax.
Factories are commonly used to create plain objects rather than class instances, which sidesteps 'this' binding concerns entirely, since there's no 'this' context to worry about losing.
Checkpoint: Does a method returned from a factory function (using closures) need this to access the factory's private data?
- →Yes, exactly like a class method would
- →No, it accesses the data directly via closure
Next, we'll explore 'The Singleton Pattern'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Use a Factory to Apply Consistent Default ARIA Configuration Across Components
A factory function that creates configuration objects for a family of related widgets (e.g. createDialogConfig()) can bake in sensible, accessible defaults (like focus trapping and escape-to-close) that every instance inherits automatically, without needing to be repeated at each usage site.
SEO Implications
- 1
No Direct SEO Effect
The Factory Pattern is a code-architecture concept; SEO relevance is limited to general maintainability benefits for the codebase.
Best Practices
Use a Factory When the Object Creation Logic Involves Conditions or Defaults
Centralizing this logic in one function avoids duplicating (and potentially desynchronizing) creation rules across every call site that needs an object of this kind.
Consider Factories Over Classes When You Don't Need Prototype-Based Inheritance
For simple objects with a fixed set of methods and no need for a broader class hierarchy, a factory function returning a plain object is often simpler and sidesteps this-binding concerns entirely.
Frequent Bugs
Duplicating the same default-value and validation logic at every location a certain kind of object is manually constructed, causing them to drift out of sync when requirements change.
Extract that shared creation logic into a single factory function used everywhere the object is needed.
Detaching a class instance's method from its instance (e.g. passing `instance.method` as a callback) and losing the correct `this` binding, causing runtime errors.
Use a factory function returning a plain object with arrow-function methods (which close over the factory's variables via closure, not `this`) to avoid this entire category of bug.
Real-World Examples
A Factory Choosing Between Multiple Payment Processor Implementations
An e-commerce checkout needed to instantiate different payment processor objects (Stripe, PayPal, etc.) based on the customer's chosen payment method, without checkout code depending on any specific processor's class directly.
function createPaymentProcessor(method) {
switch (method) {
case 'stripe': return new StripeProcessor();
case 'paypal': return new PayPalProcessor();
default: throw new Error(`Unsupported payment method: ${method}`);
}
}