Factory Pattern
Order by Description, Not Assembly
LinkedIn Hook
You walk into a pizza shop and say "one Margherita." You do not knead dough, you do not roll out tomato sauce, you do not calibrate the oven.
That is the Factory pattern in a single sentence: you describe what you want, the factory handles the complexity, and you get back a ready-to-use object. No constructors with 12 arguments, no mysterious default values scattered across your codebase, no "new UserAdmin" vs "new UserViewer" vs "new UserEditor" branching leaking into every caller.
In this lesson you will implement simple factories, UI component factories, and the Abstract Factory — the one interviewers love to ask about and most candidates fumble. You will also learn the exact line where a Factory starts beating
new.Read the full lesson -> [link]
#JavaScript #FactoryPattern #DesignPatterns #InterviewPrep #CleanCode #Frontend #WebDevelopment
What You'll Learn
- How a simple factory function encapsulates branching and defaults for object creation
- The difference between a factory function and a constructor, and when each wins
- How the Abstract Factory creates entire families of related objects
The Pizza Order Analogy
Imagine a pizza restaurant. You say "I want a Margherita" — the kitchen handles all the complexity: kneading the dough, prepping the sauce, choosing the cheese, baking it at the right temperature. You don't build the pizza yourself; you just specify the type, and the factory produces it. The Factory pattern works the same way: you describe what you want, and it handles the creation complexity.
Simple Factory Function
// Factory that creates different user types based on role
function createUser(role, name, email) {
const baseUser = {
name,
email,
createdAt: new Date(),
isActive: true
};
switch (role) {
case "admin":
return {
...baseUser,
role: "admin",
permissions: ["read", "write", "delete", "manage-users"],
canAccessDashboard: true,
canManageUsers: true,
maxStorageGB: 100
};
case "editor":
return {
...baseUser,
role: "editor",
permissions: ["read", "write"],
canAccessDashboard: true,
canManageUsers: false,
maxStorageGB: 50
};
case "viewer":
return {
...baseUser,
role: "viewer",
permissions: ["read"],
canAccessDashboard: false,
canManageUsers: false,
maxStorageGB: 5
};
default:
throw new Error(`Unknown role: ${role}`);
}
}
// Usage — caller doesn't worry about permission logic
const admin = createUser("admin", "Alice", "alice@company.com");
const editor = createUser("editor", "Bob", "bob@company.com");
const viewer = createUser("viewer", "Charlie", "charlie@company.com");
console.log(admin.permissions); // ["read", "write", "delete", "manage-users"]
console.log(editor.maxStorageGB); // 50
console.log(viewer.canAccessDashboard); // false
Factory Function vs Constructor
// Constructor approach
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.type = "sedan";
}
}
// Factory function approach — more flexible
function createVehicle(type, options) {
const defaults = {
fuel: "gasoline",
transmission: "automatic",
createdAt: Date.now()
};
const vehicles = {
sedan: { ...defaults, ...options, type: "sedan", doors: 4 },
suv: { ...defaults, ...options, type: "suv", doors: 4, allWheelDrive: true },
truck: { ...defaults, ...options, type: "truck", doors: 2, payload: "1 ton" },
electric: { ...defaults, ...options, type: "electric", fuel: "electric", battery: "100kWh" }
};
if (!vehicles[type]) {
throw new Error(`Unknown vehicle type: ${type}`);
}
return vehicles[type];
}
const myCar = createVehicle("electric", { make: "Tesla", model: "Model 3" });
// { type: "electric", fuel: "electric", battery: "100kWh", make: "Tesla", ... }
UI Component Factory
// Factory for creating UI components
function createComponent(type, props = {}) {
const baseComponent = {
id: `component-${Date.now()}-${Math.random().toString(36).slice(2)}`,
visible: true,
render() {
console.log(`Rendering ${this.type}: ${this.id}`);
},
destroy() {
this.visible = false;
console.log(`Destroyed: ${this.id}`);
}
};
switch (type) {
case "button":
return {
...baseComponent,
type: "button",
label: props.label || "Click Me",
variant: props.variant || "primary",
disabled: props.disabled || false,
onClick: props.onClick || (() => {}),
render() {
return `<button class="btn-${this.variant}" ${this.disabled ? "disabled" : ""}>${this.label}</button>`;
}
};
case "input":
return {
...baseComponent,
type: "input",
placeholder: props.placeholder || "",
value: props.value || "",
inputType: props.inputType || "text",
validate(value) {
if (props.required && !value) return "Required";
if (props.pattern && !props.pattern.test(value)) return "Invalid format";
return null;
},
render() {
return `<input type="${this.inputType}" placeholder="${this.placeholder}" value="${this.value}" />`;
}
};
case "modal":
return {
...baseComponent,
type: "modal",
title: props.title || "Modal",
content: props.content || "",
isOpen: false,
open() { this.isOpen = true; },
close() { this.isOpen = false; },
render() {
if (!this.isOpen) return "";
return `<div class="modal"><h2>${this.title}</h2><p>${this.content}</p></div>`;
}
};
default:
throw new Error(`Unknown component type: ${type}`);
}
}
// Usage
const submitBtn = createComponent("button", {
label: "Submit",
variant: "primary",
onClick: () => console.log("Submitted!")
});
const emailInput = createComponent("input", {
placeholder: "Enter email",
inputType: "email",
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
});
const confirmModal = createComponent("modal", {
title: "Are you sure?",
content: "This action cannot be undone."
});
Abstract Factory Concept
// Abstract Factory — factory that creates families of related objects
const UIThemeFactory = {
dark: {
createButton(label) {
return { label, bg: "#333", color: "#fff", border: "1px solid #555" };
},
createInput(placeholder) {
return { placeholder, bg: "#222", color: "#eee", border: "1px solid #444" };
},
createCard(title) {
return { title, bg: "#2a2a2a", color: "#fff", shadow: "0 2px 8px rgba(0,0,0,0.5)" };
}
},
light: {
createButton(label) {
return { label, bg: "#fff", color: "#333", border: "1px solid #ddd" };
},
createInput(placeholder) {
return { placeholder, bg: "#fafafa", color: "#333", border: "1px solid #ccc" };
},
createCard(title) {
return { title, bg: "#fff", color: "#333", shadow: "0 2px 8px rgba(0,0,0,0.1)" };
}
}
};
// Use the appropriate factory based on user preference
function buildUI(theme = "dark") {
const factory = UIThemeFactory[theme];
return {
header: factory.createButton("Menu"),
search: factory.createInput("Search..."),
content: factory.createCard("Welcome")
};
}
const darkUI = buildUI("dark");
const lightUI = buildUI("light");
When Factory Beats new
- Complex creation logic — when building an object requires many steps, defaults, or validation
- Dynamic types — when the type is determined at runtime (e.g., role-based user creation)
- Encapsulation — when the consumer shouldn't know or care about the creation details
- Consistency — when every instance needs shared defaults or setup logic
Common Mistakes
- Returning different shapes (admin has
permissions, viewer does not havemaxStorageGB) and letting consumers guess — always keep a stable base shape, diverge only on truly type-specific fields. - Writing a factory with a giant
switchthat never changes — if the branches never grow, a direct constructor or plain object literal is simpler and more readable. - Forgetting to throw on unknown types — silently returning
undefinedpushes the error to a mysterious line hundreds of milliseconds later.
Interview Questions
Q: What is the Factory Pattern? When would you use it over new?
The Factory pattern encapsulates object creation logic in a function or method, rather than using
newdirectly. Use it when: creation logic is complex, the type of object is determined at runtime, you want to enforce defaults, or you need to return different types based on input. It decouples consumers from the specific classes being created.
Q: What is the difference between a Factory Function and a Constructor?
A factory function is a regular function that returns a new object. A constructor uses
newandthis. Key differences: factory functions can return different types, don't requirenew, can leverage closures for private state, and are easier to compose. Constructors create instances linked to a prototype chain and supportinstanceofchecks.
Q: What is an Abstract Factory?
An Abstract Factory is a factory that creates families of related objects without specifying their concrete types. For example, a UI theme factory might have
createButton(),createInput(), andcreateCard()methods — a "dark" factory and a "light" factory both implement the same interface but produce differently styled components.
Quick Reference — Cheat Sheet
FACTORY — QUICK MAP
Shape:
function createX(type, props) {
const base = { ...defaults };
switch (type) { ... }
return { ...base, ...typeSpecific };
}
Factory fn vs Constructor:
- no `new` needed
- can return different shapes/types
- easy closure-based privacy
- constructors get `instanceof` + prototype chain
Abstract Factory:
family of related factories behind one interface
e.g. UIThemeFactory.dark.createButton / .light.createButton
Use when:
- creation is complex, multi-step, validated
- type decided at runtime
- shared defaults / setup logic
Previous: Observer / PubSub -> Subscribe, Don't Poll Next: Event Emitter -> Production-Grade Observer
This is Lesson 12.4 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.