Limited time: AI code review, hints, mock interviews, whiteboard analysis, and all Pro features are unlocked. Enroll
โฑ๏ธ 8 min read

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


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

  1. โ€œ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.

  2. โ€œ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.

  3. โ€œ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.

  4. โ€œ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

Free system design + DSA prep. If it helped you crack an interview, consider supporting.

SensAI SensAI
Beta
Listening...
Tap mic to stop voice mode

Shape what we build next

Every piece of feedback is read by the team and directly influences our roadmap.

What type of feedback?

Install SystemCraft

Add to your home screen for instant access, offline reading, and a distraction-free experience.

Offline reading Faster loads No browser tabs App-like feel

Unlock AI Features

One click to activate - no payment, no credit card. Just sign in and you're in.

AI code review and hints
SensAI chat assistant
AI mock interviews
Whiteboard analysis
100% free during early access