The JavaScript Factory Pattern: Complete Guide
The factory pattern is a creational design pattern that lets you create objects without exposing the instantiation logic. Learn how it works, when to use it, and how it simplifies object creation in JavaScript.
A factory function is a function that creates and returns a new object. Unlike constructors called with new, a factory function is just a regular function call. You pass in arguments, and it hands you back a fully formed object. The caller never sees how the object was built.
The name comes from a real factory: you place an order (call the function with parameters), and the factory delivers a finished product (the object). You do not need to know the assembly line details.
Why Use a Factory Instead of a Constructor
Here is the problem factories solve. Say you are building objects that need validation, defaults, or conditional setup before they are ready to use:
function createUser(name, role) {
// Validate inputs
if (!name || typeof name !== "string") {
throw new Error("User must have a valid name");
}
// Set defaults and derived values
const createdAt = new Date().toISOString();
const isAdmin = role === "admin";
return {
name,
role: role || "viewer",
isAdmin,
createdAt,
describe() {
return `${name} (${this.role}) -- created ${this.createdAt}`;
}
};
}
const alice = createUser("Alice", "admin");
console.log(alice.describe());
// Alice (admin) -- created 2026-07-15T...The caller writes createUser("Alice", "admin") and gets back a ready-to-use object. All the validation, default logic, and derived properties live inside the factory, not scattered across every place that creates a user.
The Core Idea: Return an Object
A factory function always returns an object. What happens inside the function is up to you. This is the simplest possible factory:
function createPoint(x, y) {
return { x, y };
}
const p = createPoint(10, 20);
console.log(p); // { x: 10, y: 20 }It looks trivial, and it is. The power comes from what you can add between the parameters and the return statement: validation, computed properties, private data, or conditional logic.
Returning Different Object Types
A factory can return completely different objects depending on its inputs. This is where factories shine compared to constructors:
function createNotifier(type) {
if (type === "email") {
return {
type: "email",
send(message) {
console.log(`Email sent: ${message}`);
}
};
}
if (type === "sms") {
return {
type: "sms",
send(message) {
console.log(`SMS sent to phone: ${message}`);
}
};
}
return {
type: "console",
send(message) {
console.log(`Log: ${message}`);
}
};
}
const emailNotifier = createNotifier("email");
emailNotifier.send("Your order shipped");
// Email sent: Your order shipped
const smsNotifier = createNotifier("sms");
smsNotifier.send("Your code is 1234");
// SMS sent to phone: Your code is 1234One function, three different object shapes. The caller only needs to know the type string. This is the strategy behind the factory: centralize creation logic so callers stay simple.
Private State with Closures
A factory function can use closures to create truly private data. Properties on the returned object are public. Variables inside the factory function are private:
function createCounter(start = 0) {
let count = start; // Private -- not on the returned object
return {
increment() {
count += 1;
return count;
},
decrement() {
count -= 1;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter(5);
console.log(counter.getCount()); // 5
console.log(counter.increment()); // 6
console.log(counter.increment()); // 7
console.log(counter.count); // undefined -- privateNobody can reach count from outside. The only way to read or change it is through the methods you returned. This is encapsulation without classes, this, or private keywords.
The factory flow makes the public-private boundary clear:
The returned methods form the public API. They are the only bridge to the private count variable. External code that tries counter.count gets undefined because count was never placed on the object.
Factory vs Constructor vs Class
JavaScript gives you several ways to create objects. Here is how they compare:
| Approach | Syntax | new required? | Prototype chain | Private data |
|---|---|---|---|---|
| Factory function | fn() returns {} | No | No (plain object) | Via closures |
| Constructor | new Fn() | Yes | Yes | No (without #) |
| Class | new Class() | Yes | Yes | With #private fields |
Factories are the simplest option when you do not need prototype inheritance. Classes are better when you need instanceof checks or shared methods on the prototype. Constructors are largely replaced by classes in modern JavaScript.
When to Use the Factory Pattern
Use a factory function when:
- Object creation involves validation, defaults, or conditional logic you want to keep in one place.
- You need to return different object shapes from the same conceptual "create" call.
- You want private data without class syntax or
WeakMap. - You are building a library and want to hide internal object structure from consumers.
- You want to avoid forcing callers to remember the
newkeyword.
Skip the factory pattern when:
- The object is simple enough that
{ x, y }is clearer thancreatePoint(x, y). - You need prototype-based inheritance or
instanceofchecks. - You are working in a codebase that consistently uses classes for consistency.
Common Mistake: Forgetting That Factories Return Plain Objects
A factory returns a plain object, not an instance of a class. This means:
function createDog(name) {
return { name, bark() { return `${name} says woof`; } };
}
const dog = createDog("Rex");
console.log(dog instanceof createDog); // false
console.log(dog.constructor); // ObjectThere is no createDog prototype. If your code relies on instanceof or constructor checks, a factory is the wrong tool. Use a JavaScript class instead.
Factory in Practice: Form Field Builder
Here is a realistic example. You are building a form system where each field type needs different validation and rendering logic:
function createField(config) {
const { type, name, label, required } = config;
const shared = { type, name, label, required };
if (type === "text") {
return {
...shared,
validate(value) {
if (required && !value) return `${label} is required`;
return null;
}
};
}
if (type === "email") {
return {
...shared,
validate(value) {
if (required && !value) return `${label} is required`;
if (value && !value.includes("@")) return `${label} must be a valid email`;
return null;
}
};
}
if (type === "number") {
return {
...shared,
min: config.min,
max: config.max,
validate(value) {
if (required && value === "") return `${label} is required`;
if (value !== "" && isNaN(Number(value))) return `${label} must be a number`;
return null;
}
};
}
throw new Error(`Unknown field type: ${type}`);
}
const emailField = createField({
type: "email",
name: "userEmail",
label: "Email address",
required: true
});
console.log(emailField.validate("")); // Email address is required
console.log(emailField.validate("bad")); // Email address must be a valid email
console.log(emailField.validate("a@b.com")); // null (valid)Each field type gets its own validation logic, but the caller just calls createField(config). Adding a new field type means adding another if block inside the factory. Nothing outside the factory changes.
This pattern pairs well with the strategy pattern when you want to swap behaviors at runtime rather than at creation time.
Rune AI
Key Insights
- A factory function is a function that returns a new object without using the new keyword.
- Factories hide object creation complexity behind a simple function call.
- You can return different object types from the same factory based on arguments.
- Factories work well with closures to create private state.
- Use factories when object creation logic is complex or conditional.
Frequently Asked Questions
What is the difference between a factory function and a constructor?
When should I use the factory pattern instead of classes?
Are factory functions slower than constructors?
Conclusion
The factory pattern is a simple but powerful way to centralize and control object creation. Use it when you have complex setup logic, need to return different object types based on conditions, or want to hide implementation details from the code that creates objects.The factory pattern is about moving object creation complexity behind a simple function call. Instead of every part of your code knowing how to build and configure an object, only the factory needs that knowledge. Start with a plain function that returns an object. Add validation inside it. Use closures for private state when you need it. Return different object shapes based on arguments when that is useful. The pattern earns its keep as soon as object creation stops being a one-liner.
More in this topic
Using Reflect and Proxy Together in JavaScript
Proxy traps intercept operations, but Reflect is what makes them behave correctly. Learn why Reflect belongs inside every trap and what breaks when you skip it.
Top JS Array Methods Interview Questions to Know
The array method questions that come up most often in JavaScript interviews, answered directly with short examples: map vs forEach, mutating vs non-mutating methods, reduce, and more.
JavaScript Reflect API: Advanced Architecture
The Reflect object exposes JavaScript's own internal operations as plain functions. Learn every Reflect method, what it returns, and why it exists as its own API.