The Factory Pattern provides an interface for creating objects, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
Difficulty: ⭐ Easy
Category: Creational Pattern
Interview Frequency: ⭐⭐⭐⭐⭐ Extremely Common
Imagine you need to create different types of objects, but:
- Object creation logic is complex
- You don't know the exact type until runtime
- You want to centralize object creation
- You need to decouple object creation from usage
Think of a car factory:
- Input: "I want an SUV"
- Factory: Handles all the complexity of building an SUV
- Output: A ready-to-use SUV
You don't need to know how to assemble a car; you just tell the factory what you want.
✅ Use Factory Pattern when:
- Object creation is complex
- Type depends on runtime conditions
- You want to hide creation logic from clients
- You need centralized object creation
- Supporting new types shouldn't require changing client code
❌ Don't use when:
- Object creation is simple (just
new MyClass()) - Only one type of object
- No conditional logic for creation
class SimpleFactory {
static create(type: string): Product {
if (type === "A") return new ProductA();
if (type === "B") return new ProductB();
throw new Error("Unknown type");
}
}abstract class Creator {
abstract factoryMethod(): Product;
operation(): void {
const product = this.factoryMethod();
product.use();
}
}Creates families of related objects (covered separately).
See factory.ts for complete implementations.
// Product interface
interface Vehicle {
drive(): void;
getType(): string;
}
// Concrete products
class Car implements Vehicle {
drive(): void {
console.log("Driving a car");
}
getType(): string {
return "Car";
}
}
class Truck implements Vehicle {
drive(): void {
console.log("Driving a truck");
}
getType(): string {
return "Truck";
}
}
// Factory
class VehicleFactory {
static createVehicle(type: "car" | "truck"): Vehicle {
switch (type) {
case "car":
return new Car();
case "truck":
return new Truck();
default:
throw new Error("Unknown vehicle type");
}
}
}
// Usage
const vehicle = VehicleFactory.createVehicle("car");
vehicle.drive(); // "Driving a car"✅ Single Responsibility: Object creation logic in one place
✅ Open/Closed Principle: Easy to add new types
✅ Loose Coupling: Clients don't depend on concrete classes
✅ Testability: Easy to mock factories
✅ Consistency: Centralized creation ensures objects are created correctly
❌ Code can become more complex with many subclasses
❌ Overkill for simple object creation
❌ Requires maintenance when adding new types
Answer:
- Factory Method: Creates ONE type of product. Uses inheritance.
abstract class Creator { abstract createProduct(): Product; }
- Abstract Factory: Creates FAMILIES of related products. Uses composition.
interface AbstractFactory { createProductA(): ProductA; createProductB(): ProductB; }
Example:
- Factory Method: DocumentFactory creates documents (PDF, Word, Excel)
- Abstract Factory: UIFactory creates related UI components (WindowsButton + WindowsCheckbox) or (MacButton + MacCheckbox)
Answer: Use Factory when:
-
Complex initialization:
// Without factory - messy const user = new User(); user.setName("John"); user.setEmail("john@example.com"); user.validate(); user.hashPassword(); // With factory - clean const user = UserFactory.create({ name: "John", email: "john@example.com" });
-
Type depends on data:
const shape = ShapeFactory.create(shapeType); // Don't know type until runtime
-
Need different implementations (testing, production):
const logger = LoggerFactory.create(isProduction ? "file" : "console");
Answer: Use polymorphism and register new types:
class ShapeFactory {
private static registry = new Map<string, () => Shape>();
static register(type: string, creator: () => Shape): void {
this.registry.set(type, creator);
}
static create(type: string): Shape {
const creator = this.registry.get(type);
if (!creator) throw new Error(`Unknown type: ${type}`);
return creator();
}
}
// Register built-in shapes
ShapeFactory.register("circle", () => new Circle());
ShapeFactory.register("square", () => new Square());
// Later, add new shape without modifying factory
ShapeFactory.register("triangle", () => new Triangle());Answer:
-
God Factory - Factory that creates too many unrelated types
// BAD - Factory does too much class ObjectFactory { create(type: string) { if (type === 'user') return new User(); if (type === 'product') return new Product(); if (type === 'order') return new Order(); // 100 more types... } } // GOOD - Separate factories class UserFactory { ... } class ProductFactory { ... } class OrderFactory { ... }
-
Exposing concrete classes - Defeats the purpose
// BAD - Client knows concrete classes const shape = ShapeFactory.createCircle(); // GOOD - Client only knows interface const shape = ShapeFactory.create("circle");
-
Not using polymorphism - Makes adding types hard
// BAD - Hard-coded types create(type: string): Shape { if (type === 'circle') return new Circle(); if (type === 'square') return new Square(); // Need to modify for new types } // GOOD - Registry pattern private registry = new Map<string, ShapeConstructor>();
Answer: Factories make testing easier:
// Production
class UserFactory {
static create(data: UserData): User {
return new User(data, new RealDatabase());
}
}
// Testing
class TestUserFactory {
static create(data: UserData): User {
return new User(data, new MockDatabase());
}
}
// Or use dependency injection
class UserFactory {
constructor(private db: Database) {}
create(data: UserData): User {
return new User(data, this.db);
}
}
// Test
const factory = new UserFactory(mockDatabase);
const user = factory.create(testData);Answer: Yes, using generics and type guards:
interface Product {
use(): void;
}
class ConcreteProductA implements Product {
use(): void {
console.log("Using Product A");
}
}
class ConcreteProductB implements Product {
use(): void {
console.log("Using Product B");
}
}
// Generic factory
class Factory<T extends Product> {
constructor(private ctor: new () => T) {}
create(): T {
return new this.ctor();
}
}
// Usage
const factoryA = new Factory(ConcreteProductA);
const productA = factoryA.create(); // Type: ConcreteProductA- Document Generation: PDF, Word, Excel documents
- Database Connections: MySQL, PostgreSQL, MongoDB
- UI Components: Cross-platform UI (Windows, Mac, Linux)
- Logging Systems: Console, File, Cloud logging
- Payment Gateways: Stripe, PayPal, Square
- Notification Services: Email, SMS, Push notifications
- Data Parsers: JSON, XML, CSV parsers
- Authentication: OAuth, JWT, SAML
function processFile(type: string, data: any) {
let parser;
if (type === "json") {
parser = new JSONParser();
parser.setValidation(true);
parser.setStrict(false);
} else if (type === "xml") {
parser = new XMLParser();
parser.setValidation(true);
parser.setNamespaceAware(true);
} else if (type === "csv") {
parser = new CSVParser();
parser.setDelimiter(",");
}
return parser.parse(data);
}class ParserFactory {
static create(type: string): Parser {
switch (type) {
case "json":
return new JSONParser({ validation: true, strict: false });
case "xml":
return new XMLParser({ validation: true, namespaceAware: true });
case "csv":
return new CSVParser({ delimiter: "," });
default:
throw new Error(`Unsupported parser: ${type}`);
}
}
}
function processFile(type: string, data: any) {
const parser = ParserFactory.create(type);
return parser.parse(data);
}class Factory {
create(type: string, params: any): Product {
const product = this.createByType(type);
product.configure(params);
return product;
}
}class Factory {
constructor(private logger: Logger, private config: Config) {}
create(): Product {
return new Product(this.logger, this.config);
}
}class LazyFactory {
private instance?: Product;
create(): Product {
if (!this.instance) {
this.instance = new Product();
}
return this.instance;
}
}- Abstract Factory: Creates families of objects
- Builder: Constructs complex objects step-by-step
- Prototype: Creates objects by cloning
- Singleton: Often used with Factory to ensure one instance
describe("Factory", () => {
test("should create correct type", () => {
const product = Factory.create("typeA");
expect(product).toBeInstanceOf(ProductA);
});
test("should throw for unknown type", () => {
expect(() => Factory.create("unknown")).toThrow();
});
test("should create independent instances", () => {
const p1 = Factory.create("typeA");
const p2 = Factory.create("typeA");
expect(p1).not.toBe(p2);
});
});The Factory Pattern is one of the most important and frequently asked patterns in interviews. Master it by:
- Understanding when object creation needs abstraction
- Practicing with real-world examples
- Knowing the difference from similar patterns
- Being able to implement quickly
Key Takeaway: "Don't call constructors directly if creation logic is complex or might change."
Next: Try the Observer Pattern →