Factory Pattern
Create Objects Without Hardcoding Their Classes
LinkedIn Hook
Here is a question that trips up a lot of developers in system design interviews:
"You have a notification system that sends Email, SMS, and Push alerts. A new channel gets added every quarter. How do you structure the object creation so adding a new channel doesn't require changing calling code?"
If your answer involves a long
if-elseblock or aswitchstatement inside the controller, you've just described a maintenance problem, not a solution.The Factory Pattern exists precisely for this. It centralizes object creation, decouples callers from concrete classes, and makes extension a matter of adding one new file, not editing three existing ones.
This lesson covers simple factory vs factory method, two real-world examples (notifications and payments), and the exact framing interviewers want to hear.
Read the full lesson with runnable code → [link]
#OOP #JavaScript #DesignPatterns #SoftwareEngineering #InterviewPrep
What You'll Learn
- What the Factory Pattern is and why it exists as a solution to a specific creation problem
- The difference between a simple factory and the factory method pattern, with clear examples of when each applies
- A notification factory (Email, SMS, Push) built both ways so you can compare them directly
- A payment factory example that mirrors real-world architecture decisions
- When to use a factory vs calling
newdirectly, and how to justify that choice in an interview
The Analogy That Makes It Click
Think about ordering a coffee at a cafe.
You walk up to the counter and say "one oat latte." You don't go behind the counter, pick up the espresso machine, steam the milk, and assemble the drink yourself. You tell the barista what you want. The barista knows how to make it. You receive the finished product.
That's the Factory Pattern.
The barista is the factory. The drink you ordered is the created object. You, the caller, have no idea whether the barista used a La Marzocco or a Breville. You don't know how the oat milk was heated. You don't care. You just know that when you say "oat latte," you get an oat latte. The creation details are someone else's problem.
Now imagine the cafe adds a new drink: matcha latte. You don't learn a new ordering process. You still walk up, say the name, and receive the drink. The factory absorbed the change. Your ordering behavior stayed the same.
That's the core value of this pattern: callers stay stable even when the set of creatable objects grows.
[INTERNAL-LINK: how abstraction hides creation details → Lesson 3.1: Abstraction]
What Is the Factory Pattern?
The Factory Pattern is a creational design pattern that delegates object instantiation to a dedicated function or class, rather than calling new directly at the point of use. The caller describes what it wants. The factory decides which class to instantiate and returns the result.
There are two main forms you'll encounter, and interviewers frequently ask you to distinguish them.
Simple Factory is a single function or static method that contains a switch or if-else block and returns one of several object types. It's not a formal GoF pattern, but it's extremely common in production code and the right starting point for most interview examples.
Factory Method is the GoF pattern. It defines a method in a base class (or interface) for creating objects, but lets each subclass override that method to decide which class to instantiate. The creation logic is polymorphic, not centralized in one block.
[INTERNAL-LINK: creational vs structural vs behavioral categories → Lesson 8.1: What are Design Patterns?]
Why Not Just Use new Directly?
This is a common interview question: "When would you use a factory instead of calling new SomeClass() yourself?"
Direct instantiation is fine when the class is fixed and the creation is simple. You know exactly what you're creating, it won't change, and there's no conditional logic involved. const date = new Date() doesn't need a factory.
A factory earns its place when one or more of these conditions apply:
- The exact class to instantiate depends on runtime data (a type string, a config value, a user's role).
- You want to add new types without modifying calling code.
- You want to centralize creation logic that would otherwise be duplicated across many files.
- You need to enforce that every created object satisfies a common interface.
[PERSONAL EXPERIENCE] In real codebases, the switch-on-type pattern appears first inside a controller or service. Once it appears in more than two places, it's a signal to extract it into a factory. The factory doesn't add complexity - it removes duplication.
Example 1 — Simple Factory: Notification System
This is the most common factory example in interviews. Three notification types (Email, SMS, Push) share a common interface. A factory function reads a type string and returns the correct instance.
// Base interface contract (enforced by convention in JS)
// Each notification class must implement: send(recipient, message)
class EmailNotification {
send(recipient, message) {
console.log(`Email to ${recipient}: ${message}`);
}
}
class SMSNotification {
send(recipient, message) {
console.log(`SMS to ${recipient}: ${message}`);
}
}
class PushNotification {
send(recipient, message) {
console.log(`Push notification to ${recipient}: ${message}`);
}
}
// Simple Factory — one function, one place to manage creation logic
function createNotification(type) {
switch (type) {
case "email":
return new EmailNotification();
case "sms":
return new SMSNotification();
case "push":
return new PushNotification();
default:
throw new Error(`Unknown notification type: ${type}`);
}
}
// Calling code never uses `new` directly on concrete classes
const notification = createNotification("email");
notification.send("alice@example.com", "Your order has shipped.");
// Output: Email to alice@example.com: Your order has shipped.
const sms = createNotification("sms");
sms.send("+1-555-0100", "Your OTP is 482910.");
// Output: SMS to +1-555-0100: Your OTP is 482910.
// Adding a new type only requires:
// 1. A new class (WhatsAppNotification)
// 2. One new case in the factory
// Calling code is untouched
Notice what the calling code knows: it knows a type string and the send() method signature. It knows nothing about EmailNotification or SMSNotification as classes. If you rename those classes, rename their files, or swap their implementations, the calling code doesn't change.
[IMAGE: Diagram showing createNotification("email" / "sms" / "push") feeding into a factory box that outputs EmailNotification, SMSNotification, PushNotification - search terms: "factory pattern object creation diagram"]
Example 2 — Simple Factory as a Static Class Method
In object-oriented code, simple factories are often organized as a static method on a class. This keeps the factory co-located with related creation logic and makes the intent explicit.
class NotificationFactory {
// Static method — no instance of NotificationFactory is ever created
static create(type) {
const registry = {
email: EmailNotification,
sms: SMSNotification,
push: PushNotification,
};
const NotificationClass = registry[type];
if (!NotificationClass) {
throw new Error(`Unknown notification type: "${type}". Valid types: ${Object.keys(registry).join(", ")}`);
}
return new NotificationClass();
}
}
// Usage
const emailNotif = NotificationFactory.create("email");
emailNotif.send("bob@example.com", "Password reset requested.");
// Output: Email to bob@example.com: Password reset requested.
const pushNotif = NotificationFactory.create("push");
pushNotif.send("device-token-xyz", "You have a new message.");
// Output: Push notification to device-token-xyz: You have a new message.
// Adding WhatsApp requires adding one entry to the registry object
// No existing code changes — open for extension, closed for modification
[UNIQUE INSIGHT] The registry object approach (mapping type strings to class references) is more maintainable than a switch block for factories with many types. You can even load the registry dynamically from a config file at startup, which is how plugin-based architectures work. The switch form is fine for 3-4 types. Once you have 8 or more, the registry pattern is clearly better.
Example 3 — Factory Method Pattern
The factory method pattern moves the creation logic out of a centralized function and into subclasses. A base class defines a createNotification() method. Each subclass overrides it to produce a specific type.
This is the GoF Factory Method pattern. Use it when the factory itself needs to vary by context, not just the product.
// Abstract base class — defines the factory method
class NotificationSender {
// Factory method — subclasses must override this
createNotification() {
throw new Error("createNotification() must be implemented by subclass");
}
// Template method — uses the factory method internally
send(recipient, message) {
const notification = this.createNotification(); // calls the subclass version
notification.send(recipient, message);
console.log(`Sent via ${this.constructor.name}`);
}
}
// Concrete senders — each overrides the factory method
class EmailSender extends NotificationSender {
createNotification() {
return new EmailNotification();
}
}
class SMSSender extends NotificationSender {
createNotification() {
return new SMSNotification();
}
}
class PushSender extends NotificationSender {
createNotification() {
return new PushNotification();
}
}
// Usage — the caller works with the base type
const sender = new EmailSender();
sender.send("carol@example.com", "Invoice ready for download.");
// Output: Email to carol@example.com: Invoice ready for download.
// Sent via EmailSender
const smsSender = new SMSSender();
smsSender.send("+1-555-0200", "Your delivery is 10 minutes away.");
// Output: SMS to +1-555-0200: Your delivery is 10 minutes away.
// Sent via SMSSender
The send() method in the base class calls this.createNotification(). It has no idea which notification class will be returned. That decision belongs entirely to the subclass. This is runtime polymorphism applied to object creation.
[INTERNAL-LINK: template method pattern and abstract base classes → Lesson 3.2: Abstract Class]
Example 4 — Payment Factory (Real-World Variation)
Payment processing is the second most common factory example in interviews. The factory receives a payment method string and returns a processor that implements a shared interface.
class CreditCardProcessor {
process(amount, currency) {
console.log(`Processing credit card payment: ${currency} ${amount}`);
// Real implementation: Stripe API call, tokenization, etc.
}
}
class PayPalProcessor {
process(amount, currency) {
console.log(`Processing PayPal payment: ${currency} ${amount}`);
// Real implementation: PayPal SDK, redirect URL generation, etc.
}
}
class CryptoProcessor {
process(amount, currency) {
console.log(`Processing crypto payment: ${currency} ${amount}`);
// Real implementation: wallet address generation, blockchain confirmation, etc.
}
}
class PaymentProcessorFactory {
static create(method) {
const processors = {
"credit-card": CreditCardProcessor,
"paypal": PayPalProcessor,
"crypto": CryptoProcessor,
};
const ProcessorClass = processors[method];
if (!ProcessorClass) {
throw new Error(`Payment method not supported: "${method}"`);
}
return new ProcessorClass();
}
}
// Order service — knows nothing about specific processor classes
function processOrder(order) {
const processor = PaymentProcessorFactory.create(order.paymentMethod);
processor.process(order.amount, order.currency);
}
processOrder({ paymentMethod: "credit-card", amount: 149.99, currency: "USD" });
// Output: Processing credit card payment: USD 149.99
processOrder({ paymentMethod: "paypal", amount: 89.00, currency: "EUR" });
// Output: Processing PayPal payment: EUR 89.00
// Adding a new payment method (e.g., Apple Pay):
// 1. Create ApplePayProcessor class
// 2. Add "apple-pay": ApplePayProcessor to the registry
// processOrder() is untouched
[CHART: Table - Simple Factory vs Factory Method - columns: Aspect, Simple Factory, Factory Method - rows: Creation location, How to add new types, Class hierarchy needed, GoF pattern, Best for]
Simple Factory vs Factory Method — When to Use Which
Both forms solve the same problem (decoupled creation) but at different scales and with different tradeoffs.
Use a simple factory when:
- The set of types is small and relatively stable.
- You want a quick win with minimal new classes.
- The creation logic is purely conditional with no further behavior needed from the factory itself.
Use the factory method pattern when:
- The factory itself has meaningful behavior beyond creating the object (like the
send()template method example above). - You need subclasses of the factory to produce different object families.
- You're working in a codebase that already uses class hierarchies extensively and consistency matters.
SIMPLE FACTORY vs FACTORY METHOD — COMPARISON
---------------------------------------------------------------------------
Aspect Simple Factory Factory Method (GoF)
---------------------------------------------------------------------------
Creation location One centralized Each subclass decides
function or static independently via
method override
How to add new type Add a case/entry to Add a new subclass
one place (factory (no existing code
function or registry) changes required)
Class hierarchy Not required Required — base class
required? (standalone function plus one subclass per
is fine) concrete type
GoF formal pattern? No (utility pattern) Yes — official GoF
creational pattern
Best for 3-8 types, simple Frameworks, plugin
config-driven selection systems, complex
hierarchies
Violates OCP? Slightly — adding a No — new types extend
type requires editing the hierarchy, not
the factory function modify existing code
---------------------------------------------------------------------------
Common Mistakes
1. Calling new on concrete classes throughout the codebase instead of using a factory.
When a type change requires hunting down 12 files to update new EmailNotification() calls, that's the problem a factory prevents. If you find yourself searching for a class name to change it everywhere, it's a factory candidate.
2. Putting business logic inside the factory. A factory's job is creation. It should not validate order amounts, check user permissions, or send analytics events. Keep the factory focused on returning a properly constructed object. Move everything else to the caller or a dedicated service.
3. Confusing simple factory with the factory method pattern.
Interviewers will probe this. A simple factory is a function or static method with a switch/registry. The factory method pattern uses a class hierarchy where subclasses override a creation method. Know both forms and be able to state the difference clearly.
4. Forgetting to throw on unknown types.
A factory that silently returns null or undefined for an unknown type hides bugs. Always throw a descriptive error with the received type and the valid options. It saves debugging time.
5. Over-engineering with a factory when new is fine.
Not every object needs a factory. If a class is always instantiated the same way, with no conditional logic and no plans to vary the type, calling new directly is cleaner. Apply the factory when variation exists, not as a default habit.
Interview Questions
Q1: What problem does the Factory Pattern solve? Why not just use new directly?
A: The Factory Pattern solves the problem of scattered, duplicated instantiation logic and tight coupling between calling code and concrete classes. When the class to instantiate depends on runtime data (a type string, a config value), or when that set of classes may grow over time, using new directly forces every caller to know about all concrete classes and to be updated whenever a new one is added. A factory centralizes that decision. Callers ask for what they need by type name. The factory decides which class to create. Adding a new type means adding one class and one factory entry, not updating every call site.
Q2: What is the difference between a simple factory and the factory method pattern?
A: A simple factory is a single function or static method that uses a switch or registry to select and instantiate the correct class. It's not a formal GoF pattern but is extremely common. The factory method pattern is a GoF creational pattern where a base class defines a creation method and each subclass overrides it to determine what gets created. Simple factory centralizes creation in one place. Factory method distributes it across a class hierarchy. Use simple factory for straightforward type selection. Use factory method when the factory itself needs to vary in behavior or when you're building extensible frameworks.
Q3: How does the Factory Pattern relate to the Open/Closed Principle?
A: The factory method pattern directly enables OCP for object creation. Adding a new notification type (WhatsApp, for example) requires creating a new WhatsAppNotification class and a new WhatsAppSender subclass. No existing class is modified. The calling code, which works against the base type NotificationSender, remains unchanged. The simple factory is slightly weaker on OCP because adding a new type requires editing the factory function itself. The registry-object variation of simple factory comes closer to OCP since you can inject new entries without modifying the factory's logic.
[INTERNAL-LINK: Open/Closed Principle in depth → Lesson 7.2: Open/Closed Principle]
Q4: Can you give a real-world scenario where you'd choose factory method over simple factory?
A: A good example is a report generation system. You have a ReportGenerator base class with a createExporter() factory method and a generate() template method that calls it. Subclasses like PDFReportGenerator and ExcelReportGenerator each override createExporter() to return their respective exporter object. The generate() method in the base class handles the common steps (fetching data, formatting) and delegates the format-specific creation to the subclass. This makes sense as factory method because the generator subclass itself has meaningful behavior beyond just picking a class - it owns the entire generation context.
Q5: What happens when the factory receives an unknown type? What should it do?
A: It should throw a descriptive error immediately, not return null, not return a default object, and not fail silently. The error message should include the received type and the list of valid types. Silent failures in factories are dangerous because they push the failure point far from the source: a null returned from a factory causes a confusing TypeError three method calls later with no indication that the factory was the root cause. A thrown error at the factory is explicit, specific, and easy to find in stack traces.
Cheat Sheet
FACTORY PATTERN — QUICK REFERENCE
======================================================================
DEFINITION
Creational pattern that delegates object instantiation to a
factory function/class, decoupling callers from concrete classes.
TWO MAIN FORMS
Simple Factory — one function/static method with a switch/registry
Factory Method — base class defines creation method, subclasses override
WHEN TO USE A FACTORY
- Class to instantiate depends on a runtime value (type string, config)
- New types are added regularly (open for extension)
- Same instantiation logic is duplicated across multiple files
- Callers should not know about concrete class names
WHEN NOT TO USE
- Class is always the same, no conditional logic needed
- Simple `new ClassName()` is readable and stable
- Over-engineering a 2-type system that won't grow
SIMPLE FACTORY — PATTERN
function createX(type) {
switch (type) {
case "a": return new A();
case "b": return new B();
default: throw new Error("Unknown type: " + type);
}
}
REGISTRY VARIATION (preferred for many types)
static create(type) {
const map = { email: Email, sms: SMS, push: Push };
const Cls = map[type];
if (!Cls) throw new Error("Unknown: " + type);
return new Cls();
}
FACTORY METHOD — PATTERN
class Creator {
createProduct() { throw new Error("Override required"); }
doWork() {
const product = this.createProduct(); // calls subclass version
product.execute();
}
}
class ConcreteCreator extends Creator {
createProduct() { return new ConcreteProduct(); }
}
INTERVIEW KEYWORDS
- "Decoupled creation"
- "Open for extension, closed for modification"
- "Centralized instantiation logic"
- "Polymorphic creation (factory method)"
- "Runtime type selection"
COMMON EXAMPLES
- Notification factory (Email, SMS, Push)
- Payment processor factory (Card, PayPal, Crypto)
- Logger factory (Console, File, Remote)
- Parser factory (JSON, XML, CSV)
- Database connection factory (MySQL, Postgres, SQLite)
SIMPLE FACTORY vs FACTORY METHOD
Simple Factory → one file, one place to change, fine for most apps
Factory Method → class hierarchy, subclasses own creation, best for frameworks
======================================================================
Navigation
- Previous: Lesson 8.2 - Singleton Pattern
- Next: Lesson 8.4 - Builder Pattern
This is Lesson 8.3 of the OOP Interview Prep Course — 8 chapters, 41 lessons.