Factory Pattern
Factory patterns encapsulate object creation logic. Instead of scattering new ConcreteClass() throughout your code, you centralize creation decisions in one place. When a new type is added, you modify the factory โ not every call site.
Why this matters: In machine coding rounds, youโll often need to create different objects based on a type parameter (vehicle type, payment method, notification channel). Factory keeps this clean and gives you a single place to add new types.
Prerequisites
- SOLID Principles โ Factory supports OCP and DIP
- Strategy Pattern โ Factories often produce strategies
The Three Variants
flowchart LR
A["Simple Factory<br/>One class with static method"]:::client
B["Factory Method<br/>Subclass decides what to create"]:::service
C["Abstract Factory<br/>Family of related objects"]:::data
A --> B
B --> C
classDef client fill:#4c3a5e,stroke:#818cf8,color:#e2e8f0
classDef service fill:#1a3a2a,stroke:#4ade80,color:#e2e8f0
classDef data fill:#3b3520,stroke:#fbbf24,color:#e2e8f0
| Variant | When to Use | Complexity |
|---|---|---|
| Simple Factory | One creation point, type decided by parameter | Low |
| Factory Method | Subclasses should decide what to instantiate | Medium |
| Abstract Factory | Need families of related objects that must be consistent | High |
Simple Factory
The most common in interviews. A single method that takes a type and returns the right object.
// Simple Factory: centralized creation logic
public class NotificationFactory {
public static NotificationSender create(NotificationType type) {
return switch (type) {
case EMAIL -> new EmailSender();
case SMS -> new SmsSender();
case PUSH -> new PushNotificationSender();
case SLACK -> new SlackSender();
};
}
}
// Usage: caller doesn't know concrete classes
public class OrderService {
public void notifyUser(Order order, NotificationType preferred) {
NotificationSender sender = NotificationFactory.create(preferred);
sender.send(order.getUserId(), buildMessage(order));
}
}
// Adding WhatsApp? Add one case to factory + new class. OrderService unchanged.
public class WhatsAppSender implements NotificationSender {
@Override
public void send(String userId, String message) {
// WhatsApp Business API call
}
}
class NotificationFactory:
_registry: dict[str, type] = {}
@classmethod
def register(cls, notification_type: str, klass: type):
cls._registry[notification_type] = klass
@classmethod
def create(cls, notification_type: str) -> NotificationSender:
klass = cls._registry.get(notification_type)
if not klass:
raise ValueError(f"Unknown notification type: {notification_type}")
return klass()
# Register implementations
NotificationFactory.register("email", EmailSender)
NotificationFactory.register("sms", SmsSender)
NotificationFactory.register("push", PushNotificationSender)
# Usage
def notify_user(order, preferred_type: str):
sender = NotificationFactory.create(preferred_type)
sender.send(order.user_id, build_message(order))
# Adding WhatsApp: register + new class
class WhatsAppSender(NotificationSender):
def send(self, user_id: str, message: str):
pass # WhatsApp API call
NotificationFactory.register("whatsapp", WhatsAppSender)
class NotificationFactory {
public:
static unique_ptr<NotificationSender> create(NotificationType type) {
switch (type) {
case NotificationType::EMAIL:
return make_unique<EmailSender>();
case NotificationType::SMS:
return make_unique<SmsSender>();
case NotificationType::PUSH:
return make_unique<PushNotificationSender>();
default:
throw invalid_argument("Unknown notification type");
}
}
};
// Usage
void OrderService::notifyUser(const Order& order, NotificationType preferred) {
auto sender = NotificationFactory::create(preferred);
sender->send(order.getUserId(), buildMessage(order));
}
Factory Method
The parent class defines the interface for creation but lets subclasses decide which class to instantiate. Useful when frameworks need to delegate creation to application-specific code.
// Abstract creator with factory method
public abstract class DocumentExporter {
// Factory method -- subclasses decide what renderer to create
protected abstract DocumentRenderer createRenderer();
// Template method uses the factory method
public final byte[] export(Document doc) {
DocumentRenderer renderer = createRenderer();
renderer.setMargins(getDefaultMargins());
renderer.renderHeader(doc.getTitle());
for (Section section : doc.getSections()) {
renderer.renderSection(section);
}
renderer.renderFooter(doc.getMetadata());
return renderer.toBytes();
}
}
// Concrete creators
public class PdfExporter extends DocumentExporter {
@Override
protected DocumentRenderer createRenderer() {
return new PdfRenderer(); // Creates PDF-specific renderer
}
}
public class HtmlExporter extends DocumentExporter {
@Override
protected DocumentRenderer createRenderer() {
return new HtmlRenderer(); // Creates HTML-specific renderer
}
}
public class MarkdownExporter extends DocumentExporter {
@Override
protected DocumentRenderer createRenderer() {
return new MarkdownRenderer();
}
}
// Usage
DocumentExporter exporter = new PdfExporter();
byte[] result = exporter.export(myDocument);
from abc import ABC, abstractmethod
class DocumentExporter(ABC):
@abstractmethod
def create_renderer(self) -> 'DocumentRenderer':
"""Factory method - subclasses decide the renderer."""
...
def export(self, doc: 'Document') -> bytes:
renderer = self.create_renderer()
renderer.set_margins(self.get_default_margins())
renderer.render_header(doc.title)
for section in doc.sections:
renderer.render_section(section)
renderer.render_footer(doc.metadata)
return renderer.to_bytes()
class PdfExporter(DocumentExporter):
def create_renderer(self):
return PdfRenderer()
class HtmlExporter(DocumentExporter):
def create_renderer(self):
return HtmlRenderer()
# Usage
exporter = PdfExporter()
result = exporter.export(my_document)
class DocumentExporter {
protected:
virtual unique_ptr<DocumentRenderer> createRenderer() = 0;
public:
virtual ~DocumentExporter() = default;
vector<uint8_t> exportDoc(const Document& doc) {
auto renderer = createRenderer();
renderer->setMargins(getDefaultMargins());
renderer->renderHeader(doc.getTitle());
for (const auto& section : doc.getSections()) {
renderer->renderSection(section);
}
return renderer->toBytes();
}
};
class PdfExporter : public DocumentExporter {
protected:
unique_ptr<DocumentRenderer> createRenderer() override {
return make_unique<PdfRenderer>();
}
};
class HtmlExporter : public DocumentExporter {
protected:
unique_ptr<DocumentRenderer> createRenderer() override {
return make_unique<HtmlRenderer>();
}
};
Abstract Factory
Creates families of related objects. When you need a Button, TextField, and Menu that all belong to the same theme (dark mode vs light mode), Abstract Factory ensures consistency.
// Abstract factory: produces a family of related objects
public interface DatabaseDriverFactory {
Connection createConnection(String url);
QueryBuilder createQueryBuilder();
MigrationRunner createMigrationRunner();
}
// Concrete factory: PostgreSQL family
public class PostgresDriverFactory implements DatabaseDriverFactory {
@Override
public Connection createConnection(String url) {
return new PostgresConnection(url);
}
@Override
public QueryBuilder createQueryBuilder() {
return new PostgresQueryBuilder(); // Handles JSONB, arrays, etc.
}
@Override
public MigrationRunner createMigrationRunner() {
return new PostgresMigrationRunner();
}
}
// Concrete factory: MySQL family
public class MySQLDriverFactory implements DatabaseDriverFactory {
@Override
public Connection createConnection(String url) {
return new MySQLConnection(url);
}
@Override
public QueryBuilder createQueryBuilder() {
return new MySQLQueryBuilder(); // Different syntax for JSON, no arrays
}
@Override
public MigrationRunner createMigrationRunner() {
return new MySQLMigrationRunner();
}
}
// Client code works with any database -- never references concrete classes
public class Repository {
private final Connection connection;
private final QueryBuilder queryBuilder;
public Repository(DatabaseDriverFactory factory, String dbUrl) {
this.connection = factory.createConnection(dbUrl);
this.queryBuilder = factory.createQueryBuilder();
}
public List<User> findActiveUsers() {
String query = queryBuilder.select("users").where("active", true).build();
return connection.execute(query, User::fromRow);
}
}
class DatabaseDriverFactory(ABC):
@abstractmethod
def create_connection(self, url: str) -> 'Connection': ...
@abstractmethod
def create_query_builder(self) -> 'QueryBuilder': ...
@abstractmethod
def create_migration_runner(self) -> 'MigrationRunner': ...
class PostgresDriverFactory(DatabaseDriverFactory):
def create_connection(self, url):
return PostgresConnection(url)
def create_query_builder(self):
return PostgresQueryBuilder()
def create_migration_runner(self):
return PostgresMigrationRunner()
class MySQLDriverFactory(DatabaseDriverFactory):
def create_connection(self, url):
return MySQLConnection(url)
def create_query_builder(self):
return MySQLQueryBuilder()
def create_migration_runner(self):
return MySQLMigrationRunner()
# Client code is database-agnostic
class Repository:
def __init__(self, factory: DatabaseDriverFactory, db_url: str):
self._conn = factory.create_connection(db_url)
self._qb = factory.create_query_builder()
class DatabaseDriverFactory {
public:
virtual ~DatabaseDriverFactory() = default;
virtual unique_ptr<Connection> createConnection(const string& url) = 0;
virtual unique_ptr<QueryBuilder> createQueryBuilder() = 0;
virtual unique_ptr<MigrationRunner> createMigrationRunner() = 0;
};
class PostgresDriverFactory : public DatabaseDriverFactory {
public:
unique_ptr<Connection> createConnection(const string& url) override {
return make_unique<PostgresConnection>(url);
}
unique_ptr<QueryBuilder> createQueryBuilder() override {
return make_unique<PostgresQueryBuilder>();
}
unique_ptr<MigrationRunner> createMigrationRunner() override {
return make_unique<PostgresMigrationRunner>();
}
};
class Repository {
unique_ptr<Connection> conn_;
unique_ptr<QueryBuilder> qb_;
public:
Repository(DatabaseDriverFactory& factory, const string& url)
: conn_(factory.createConnection(url)),
qb_(factory.createQueryBuilder()) {}
};
When to Use vs When to Avoid
| Use Factory When | Avoid When |
|---|---|
| Object creation depends on a type parameter | Direct new is simple and obvious |
| You want to hide concrete class names from callers | Only one implementation exists |
| Creation logic is complex (config, validation, setup) | Constructor is trivial |
| New types will be added over time | Type set is fixed and tiny |
| Testing needs mock/stub objects | Mocking isnโt a concern |
Interview Questions
-
โSimple Factory vs Factory Method โ when to use which?โ โ Simple Factory is a single class with a creation method. Factory Method uses inheritance where subclasses override the creation. Use Factory Method when the framework doesnโt know what objects the application needs.
-
โHow does Factory relate to Dependency Inversion?โ โ Factory creates concrete objects, but clients only see the interface. This inverts the dependency: high-level code depends on abstractions, not concrete classes.
-
โCan you combine Factory with Strategy?โ โ Absolutely. A Factory creates the right Strategy based on configuration or runtime state. This is extremely common in LLD problems.
-
โWhat about a Registry-based factory?โ โ Instead of a switch, use a Map from type to creator function. New types register themselves without modifying the factory. Pythonโs example above shows this.
See It in Action
| Parking Lot | URL Shortener |