Design Pattern Interview QA - 2

10 design pattern interview questions covering Spring Framework dependency injection, Inversion of Control, Template Method, State, Facade, Flyweight, Mediator, Repository, MVC, and CQRS patterns with Java and Python examples.
Author
Published

21 May 2026

Keywords

Spring Framework dependency injection, Inversion of Control, Spring IoC container, template method pattern, state pattern, facade pattern, flyweight pattern, mediator pattern, repository pattern, MVC pattern, CQRS pattern, Spring Boot design patterns

Introduction

This is Part 2 of our Design Pattern Interview QA series, focused on dependency injection frameworks (Spring), Inversion of Control, and additional enterprise patterns frequently asked in backend and full-stack interviews.

For the 10 most commonly asked GoF patterns (Singleton, Factory, Observer, Strategy, Decorator, Adapter, Builder, Command, Chain of Responsibility, Proxy), see Design Pattern Interview QA - 1.


Q1: What is Dependency Injection and how do modern frameworks implement it?

Answer:

Dependency Injection (DI) is a design pattern where an object’s dependencies are provided from the outside rather than created internally. Frameworks such as Spring and FastAPI manage object creation, wiring, and lifecycle automatically.

graph TD
    linkStyle default stroke:#000,color:#000
    subgraph Without["Without DI (Tight Coupling)"]
        A["OrderService"] -->|"new"| B["MySQLOrderRepo()"]
        A -->|"new"| C["SmtpEmailService()"]
    end

    subgraph With["With DI (Spring Container)"]
        CONTAINER["Spring IoC Container"]
        CONTAINER -->|"injects"| D["OrderService"]
        CONTAINER -->|"creates"| E["OrderRepository"]
        CONTAINER -->|"creates"| F["EmailService"]
        E -.->|"injected into"| D
        F -.->|"injected into"| D
    end

    style Without fill:#ff9f84,stroke:#333,color:#222
    style With fill:#8fe0bf,stroke:#333,color:#222
    style CONTAINER fill:#9fddea,stroke:#333,color:#222

Python DI: Three Injection Styles

from dataclasses import dataclass
from typing import Protocol

class OrderRepository(Protocol):
    def save(self, order: "Order") -> "Order": ...

class EmailService(Protocol):
    def send_confirmation(self, order: "Order") -> None: ...

@dataclass
class Order:
    id: int | None
    item: str

@dataclass
class OrderRequest:
    item: str

# 1. CONSTRUCTOR INJECTION (recommended)
class OrderService:
    def __init__(self, order_repo: OrderRepository, email_service: EmailService):
        self.order_repo = order_repo
        self.email_service = email_service

    def create_order(self, request: OrderRequest) -> Order:
        order = self.order_repo.save(Order(id=None, item=request.item))
        self.email_service.send_confirmation(order)
        return order

# 2. PARAMETER/FUNCTION INJECTION (common in Python)
def generate_report(data: list[dict], formatter=None) -> str:
    if formatter is not None:
        return formatter(data)
    return str(data)

# 3. FRAMEWORK INJECTION (FastAPI Depends)
from fastapi import Depends, FastAPI
from typing import Annotated

app = FastAPI()

def get_order_service() -> OrderService:
    return OrderService(order_repo=SqlOrderRepo(), email_service=SmtpEmailService())

@app.post("/orders")
def create_order(
    request: OrderRequest,
    service: Annotated[OrderService, Depends(get_order_service)],
):
    return service.create_order(request)

Python Dependency Scope and Lifecycle

from collections.abc import Generator

# App-level singleton dependency (created once)
class Settings:
    def __init__(self):
        self.env = "production"

settings = Settings()

# Request-scoped dependency with cleanup (FastAPI)
def get_db() -> Generator["Database", None, None]:
    db = Database("postgresql://localhost/app")
    try:
        yield db  # Injected into handlers
    finally:
        db.close()  # Lifecycle cleanup

# Transient/prototype behavior: create each time
def build_request_handler() -> "RequestHandler":
    return RequestHandler()

Python Configuration Approaches

from pydantic_settings import BaseSettings

# Approach 1: Settings class from env/.env
class AppSettings(BaseSettings):
    db_url: str = "sqlite:///app.db"
    email_backend: str = "smtp"

settings = AppSettings()

# Approach 2: Dependency provider functions
def get_order_repository() -> "OrderRepository":
    return SqlOrderRepo(settings.db_url)

def get_email_service() -> "EmailService":
    if settings.email_backend == "smtp":
        return SmtpEmailService()
    return MockEmailService()

# Approach 3: Environment-based wiring
def get_payment_gateway() -> "PaymentGateway":
    if settings.env == "production":
        return StripeGateway()
    return FakePaymentGateway()

Testing Python DI

from unittest.mock import Mock

# Unit test — inject mocks manually
def test_create_order_unit():
    mock_repo = Mock()
    mock_email = Mock()
    service = OrderService(mock_repo, mock_email)

    mock_repo.save.return_value = Order(id=1, item="Widget")

    order = service.create_order(OrderRequest(item="Widget"))

    mock_repo.save.assert_called_once()
    mock_email.send_confirmation.assert_called_once_with(order)

# Integration-style test — override FastAPI dependencies
from fastapi.testclient import TestClient

def test_create_order_api(app):
    fake_service = Mock()
    fake_service.create_order.return_value = {"id": 10, "item": "Gadget"}

    app.dependency_overrides[get_order_service] = lambda: fake_service
    client = TestClient(app)

    res = client.post("/orders", json={"item": "Gadget"})
    assert res.status_code == 200
    app.dependency_overrides.clear()

Why Constructor Injection Is Preferred

Injection Type Pros Cons
Constructor Immutable fields; easy to test; fail-fast on missing deps Verbose with many dependencies
Setter Good for optional deps; reconfigurable Mutable; easy to forget injection
Field Concise Cannot test without reflection; hides dependencies

Q2: What is Inversion of Control (IoC) and how does it relate to DI?

Answer:

Inversion of Control (IoC) is a broad principle where the flow of control is inverted — instead of your code controlling the creation and wiring of objects, a framework or container takes over. Dependency Injection is one specific implementation of IoC.

graph TD
    linkStyle default stroke:#000,color:#000
    subgraph Traditional["Traditional Control Flow"]
        APP["Your Code"]
        APP -->|"creates"| DEP1["Database"]
        APP -->|"creates"| DEP2["Logger"]
        APP -->|"calls"| FRAMEWORK["Framework"]
    end

    subgraph IoC["Inversion of Control"]
        CONTAINER["IoC Container<br/>(Spring / FastAPI)"]
        CONTAINER -->|"creates & injects"| DEP3["Database"]
        CONTAINER -->|"creates & injects"| DEP4["Logger"]
        CONTAINER -->|"calls"| YOUR_CODE["Your Code"]
    end

    style Traditional fill:#ff9f84,stroke:#333,color:#222
    style IoC fill:#8fe0bf,stroke:#333,color:#222
    style CONTAINER fill:#9fddea,stroke:#333,color:#222

IoC Implementations

IoC Type Mechanism Example
Dependency Injection Container injects dependencies Spring @Autowired, FastAPI Depends()
Service Locator Object asks container for deps ServiceLocator.get(UserRepo.class)
Template Method Framework calls your overrides HttpServlet.doGet(), Django views
Event-driven Framework calls handlers Spring @EventListener, Observer pattern
Strategy via config Framework picks implementation Spring profiles, plugin systems

Spring IoC Container Architecture

// The Spring ApplicationContext IS the IoC container
// It manages the full lifecycle:

// 1. BEAN DEFINITION: Read configuration (annotations, XML, Java config)
// 2. INSTANTIATION: Create bean instances
// 3. DEPENDENCY RESOLUTION: Wire dependencies (DI)
// 4. INITIALIZATION: Call @PostConstruct, InitializingBean
// 5. READY: Beans available for use
// 6. DESTRUCTION: Call @PreDestroy on shutdown

// Two main container types:
// BeanFactory — basic DI, lazy initialization
// ApplicationContext — extends BeanFactory with:
//   - Event publishing
//   - Internationalization (i18n)
//   - Resource loading
//   - AOP integration

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        // Creates the IoC container
        ApplicationContext ctx = SpringApplication.run(MyApp.class, args);

        // Container manages all beans
        OrderService service = ctx.getBean(OrderService.class);
        service.createOrder(new OrderRequest("Widget"));
    }
}

IoC in Python (FastAPI Depends)

from fastapi import FastAPI, Depends
from typing import Annotated

app = FastAPI()

# Dependencies are functions — FastAPI's IoC container calls them
def get_db():
    db = Database("postgres://localhost/mydb")
    try:
        yield db  # Injected into endpoint
    finally:
        db.close()  # Cleanup (like @PreDestroy)

def get_user_service(db: Database = Depends(get_db)):
    return UserService(db)  # Composed dependency

# FastAPI resolves the entire dependency tree (IoC)
@app.get("/users/{user_id}")
async def get_user(
    user_id: int,
    service: Annotated[UserService, Depends(get_user_service)],
):
    return service.get_by_id(user_id)

DI vs Service Locator

Aspect Dependency Injection Service Locator
Coupling Low — deps are explicit in constructor Medium — class depends on locator
Testability Easy — pass mocks directly Harder — must configure locator for tests
Transparency Dependencies visible in API Dependencies hidden inside methods
Spring approach @Autowired / constructor ApplicationContext.getBean() (discouraged)

Q3: What is the Template Method Pattern?

Answer:

The Template Method pattern defines the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the algorithm’s structure. It’s a key pattern behind many frameworks (Spring, Django, JUnit).

graph TD
    linkStyle default stroke:#000,color:#000
    ABSTRACT["AbstractClass<br/>(defines template)"]
    ABSTRACT --> STEP1["step1() — fixed"]
    ABSTRACT --> STEP2["step2() — abstract<br/>(subclass overrides)"]
    ABSTRACT --> STEP3["step3() — abstract<br/>(subclass overrides)"]
    ABSTRACT --> STEP4["step4() — fixed"]

    CONCRETE_A["ConcreteClassA<br/>overrides step2, step3"]
    CONCRETE_B["ConcreteClassB<br/>overrides step2, step3"]

    ABSTRACT -.-> CONCRETE_A
    ABSTRACT -.-> CONCRETE_B

    style ABSTRACT fill:#8fe0bf,stroke:#333,color:#222
    style CONCRETE_A fill:#9fddea,stroke:#333,color:#222
    style CONCRETE_B fill:#ffe29a,stroke:#333,color:#222

Java Implementation

// Template method in an abstract class
public abstract class DataExporter {

    // TEMPLATE METHOD — defines the algorithm skeleton
    // Marked final so subclasses can't change the overall flow
    public final void export(String query) {
        List<Map<String, Object>> data = fetchData(query);   // Step 1
        List<Map<String, Object>> transformed = transform(data); // Step 2
        String formatted = format(transformed);               // Step 3
        write(formatted);                                     // Step 4
        cleanup();                                            // Step 5 (hook)
    }

    // Concrete step — same for all subclasses
    private List<Map<String, Object>> fetchData(String query) {
        return database.query(query);
    }

    // Abstract steps — subclasses MUST implement
    protected abstract List<Map<String, Object>> transform(
        List<Map<String, Object>> data
    );
    protected abstract String format(List<Map<String, Object>> data);
    protected abstract void write(String content);

    // Hook method — optional override (default does nothing)
    protected void cleanup() { }
}

// Concrete implementation: CSV exporter
public class CsvExporter extends DataExporter {

    @Override
    protected List<Map<String, Object>> transform(List<Map<String, Object>> data) {
        // Remove sensitive columns
        data.forEach(row -> row.remove("password"));
        return data;
    }

    @Override
    protected String format(List<Map<String, Object>> data) {
        StringBuilder sb = new StringBuilder();
        // Header
        sb.append(String.join(",", data.get(0).keySet())).append("\n");
        // Rows
        data.forEach(row ->
            sb.append(String.join(",",
                row.values().stream().map(Object::toString).toList()
            )).append("\n")
        );
        return sb.toString();
    }

    @Override
    protected void write(String content) {
        Files.writeString(Path.of("export.csv"), content);
    }
}

// Concrete implementation: JSON exporter
public class JsonExporter extends DataExporter {

    @Override
    protected List<Map<String, Object>> transform(List<Map<String, Object>> data) {
        return data; // No transformation needed for JSON
    }

    @Override
    protected String format(List<Map<String, Object>> data) {
        return new ObjectMapper().writeValueAsString(data);
    }

    @Override
    protected void write(String content) {
        Files.writeString(Path.of("export.json"), content);
    }
}

// Usage — algorithm structure is fixed, details vary
DataExporter csvExporter = new CsvExporter();
csvExporter.export("SELECT * FROM users");

DataExporter jsonExporter = new JsonExporter();
jsonExporter.export("SELECT * FROM users");

Python Implementation

from abc import ABC, abstractmethod

class DataPipeline(ABC):
    """Template method: extract → validate → transform → load."""

    def run(self, source: str) -> int:
        """Template method — defines the pipeline skeleton."""
        raw = self.extract(source)
        valid = self.validate(raw)
        transformed = self.transform(valid)
        count = self.load(transformed)
        self.on_complete(count)  # Hook
        return count

    @abstractmethod
    def extract(self, source: str) -> list[dict]: ...

    def validate(self, data: list[dict]) -> list[dict]:
        """Default validation — subclasses can override."""
        return [row for row in data if row]  # Remove empty rows

    @abstractmethod
    def transform(self, data: list[dict]) -> list[dict]: ...

    @abstractmethod
    def load(self, data: list[dict]) -> int: ...

    def on_complete(self, count: int) -> None:
        """Hook — optional override."""
        pass

class CsvToPostgresPipeline(DataPipeline):

    def extract(self, source: str) -> list[dict]:
        import csv
        with open(source) as f:
            return list(csv.DictReader(f))

    def transform(self, data: list[dict]) -> list[dict]:
        for row in data:
            row["email"] = row["email"].lower().strip()
        return data

    def load(self, data: list[dict]) -> int:
        db.bulk_insert("users", data)
        return len(data)

    def on_complete(self, count: int) -> None:
        print(f"Loaded {count} records into PostgreSQL")

# Run the pipeline
pipeline = CsvToPostgresPipeline()
pipeline.run("users.csv")

Template Method in Frameworks

Framework Template Method You Override
Spring MVC DispatcherServlet.doDispatch() @Controller methods
JUnit TestCase.runBare() @Test, @BeforeEach
Django View.dispatch() get(), post()
Python unittest TestCase.run() setUp(), test_*()
Java Servlets HttpServlet.service() doGet(), doPost()

Q4: What is the State Pattern?

Answer:

The State pattern allows an object to alter its behavior when its internal state changes. The object appears to change its class. It’s used to replace complex if/else or switch chains for state-dependent behavior.

graph TD
    linkStyle default stroke:#000,color:#000
    CONTEXT["Context<br/>(Order)"]
    CONTEXT --> STATE["Current State"]
    STATE --> S1["PendingState"]
    STATE --> S2["PaidState"]
    STATE --> S3["ShippedState"]
    STATE --> S4["DeliveredState"]
    STATE --> S5["CancelledState"]

    S1 -->|"pay()"| S2
    S2 -->|"ship()"| S3
    S3 -->|"deliver()"| S4
    S1 -->|"cancel()"| S5

    style CONTEXT fill:#8fe0bf,stroke:#333,color:#222
    style STATE fill:#ffe29a,stroke:#333,color:#222

Implementation: Order State Machine

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from order import Order

# State interface
class OrderState(ABC):
    @abstractmethod
    def pay(self, order: "Order") -> None: ...

    @abstractmethod
    def ship(self, order: "Order") -> None: ...

    @abstractmethod
    def deliver(self, order: "Order") -> None: ...

    @abstractmethod
    def cancel(self, order: "Order") -> None: ...

# Concrete states
class PendingState(OrderState):
    def pay(self, order: "Order") -> None:
        print("Payment processed. Order is now paid.")
        order.set_state(PaidState())

    def ship(self, order: "Order") -> None:
        raise InvalidOperationError("Cannot ship — order not yet paid")

    def deliver(self, order: "Order") -> None:
        raise InvalidOperationError("Cannot deliver — order not yet shipped")

    def cancel(self, order: "Order") -> None:
        print("Order cancelled.")
        order.set_state(CancelledState())

class PaidState(OrderState):
    def pay(self, order: "Order") -> None:
        raise InvalidOperationError("Order already paid")

    def ship(self, order: "Order") -> None:
        print("Order shipped.")
        order.set_state(ShippedState())

    def deliver(self, order: "Order") -> None:
        raise InvalidOperationError("Cannot deliver — order not yet shipped")

    def cancel(self, order: "Order") -> None:
        print("Refund issued. Order cancelled.")
        order.set_state(CancelledState())

class ShippedState(OrderState):
    def pay(self, order: "Order") -> None:
        raise InvalidOperationError("Order already paid")

    def ship(self, order: "Order") -> None:
        raise InvalidOperationError("Order already shipped")

    def deliver(self, order: "Order") -> None:
        print("Order delivered!")
        order.set_state(DeliveredState())

    def cancel(self, order: "Order") -> None:
        raise InvalidOperationError("Cannot cancel — order already shipped")

class DeliveredState(OrderState):
    def pay(self, order): raise InvalidOperationError("Order completed")
    def ship(self, order): raise InvalidOperationError("Order completed")
    def deliver(self, order): raise InvalidOperationError("Already delivered")
    def cancel(self, order): raise InvalidOperationError("Cannot cancel delivered order")

class CancelledState(OrderState):
    def pay(self, order): raise InvalidOperationError("Order cancelled")
    def ship(self, order): raise InvalidOperationError("Order cancelled")
    def deliver(self, order): raise InvalidOperationError("Order cancelled")
    def cancel(self, order): raise InvalidOperationError("Already cancelled")

class InvalidOperationError(Exception):
    pass

# Context
class Order:
    def __init__(self, order_id: str):
        self.order_id = order_id
        self._state: OrderState = PendingState()

    def set_state(self, state: OrderState) -> None:
        self._state = state

    @property
    def status(self) -> str:
        return type(self._state).__name__.replace("State", "")

    def pay(self) -> None:
        self._state.pay(self)

    def ship(self) -> None:
        self._state.ship(self)

    def deliver(self) -> None:
        self._state.deliver(self)

    def cancel(self) -> None:
        self._state.cancel(self)

# Usage
order = Order("ORD-001")
print(order.status)  # "Pending"

order.pay()
print(order.status)  # "Paid"

order.ship()
print(order.status)  # "Shipped"

order.deliver()
print(order.status)  # "Delivered"

# Invalid transition raises error
try:
    order.cancel()  # InvalidOperationError: Cannot cancel delivered order
except InvalidOperationError as e:
    print(f"Error: {e}")

State vs Strategy

Aspect State Strategy
Purpose Object changes behavior as state changes Client chooses algorithm
Transitions States know about and trigger transitions Client explicitly sets strategy
Awareness States may know about each other Strategies are independent
Example Order lifecycle, TCP connection Sorting algorithm, compression

Q5: What is the Facade Pattern?

Answer:

The Facade pattern provides a simplified interface to a complex subsystem. It doesn’t hide the subsystem — it provides a convenient default interface while still allowing direct access when needed.

graph TD
    linkStyle default stroke:#000,color:#000
    CLIENT["Client"]
    CLIENT --> FACADE["Facade<br/>(simple interface)"]
    FACADE --> S1["VideoCodec"]
    FACADE --> S2["AudioCodec"]
    FACADE --> S3["Muxer"]
    FACADE --> S4["FileWriter"]
    FACADE --> S5["MetadataParser"]

    CLIENT2["Advanced Client"]
    CLIENT2 --> S1
    CLIENT2 --> S3

    style FACADE fill:#8fe0bf,stroke:#333,color:#222
    style CLIENT fill:#9fddea,stroke:#333,color:#222

Implementation

# Complex subsystem classes
class VideoCodec:
    def decode(self, file: str) -> bytes:
        print(f"Decoding video from {file}")
        return b"video_data"

    def encode(self, data: bytes, format: str) -> bytes:
        print(f"Encoding video to {format}")
        return b"encoded_video"

class AudioCodec:
    def extract(self, file: str) -> bytes:
        print(f"Extracting audio from {file}")
        return b"audio_data"

    def encode(self, data: bytes, bitrate: int) -> bytes:
        print(f"Encoding audio at {bitrate}kbps")
        return b"encoded_audio"

class Muxer:
    def mux(self, video: bytes, audio: bytes) -> bytes:
        print("Multiplexing audio and video streams")
        return b"muxed_data"

class FileWriter:
    def write(self, data: bytes, output: str) -> None:
        print(f"Writing to {output}")

# FACADE — simple interface to the complex subsystem
class VideoConverter:
    """Facade that hides the complexity of video conversion."""

    def __init__(self):
        self._video = VideoCodec()
        self._audio = AudioCodec()
        self._muxer = Muxer()
        self._writer = FileWriter()

    def convert(self, input_file: str, output_file: str, format: str = "mp4") -> None:
        """One method does everything — clients don't need to know the steps."""
        video_data = self._video.decode(input_file)
        audio_data = self._audio.extract(input_file)

        encoded_video = self._video.encode(video_data, format)
        encoded_audio = self._audio.encode(audio_data, bitrate=192)

        muxed = self._muxer.mux(encoded_video, encoded_audio)
        self._writer.write(muxed, output_file)
        print(f"Conversion complete: {output_file}")

# Client uses the simple facade
converter = VideoConverter()
converter.convert("input.avi", "output.mp4")
# vs manually orchestrating 5 subsystem objects

Java: Spring Boot as a Facade

// Spring Boot itself is a FACADE over the Spring ecosystem!
// Instead of manually configuring:
//   - DispatcherServlet
//   - ViewResolver
//   - DataSource + JPA EntityManager
//   - Transaction manager
//   - Embedded server (Tomcat)

// You just write:
@SpringBootApplication  // Facade annotation — auto-configures everything
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// Service facade example:
@Service
public class CheckoutFacade {

    private final InventoryService inventory;
    private final PaymentService payment;
    private final ShippingService shipping;
    private final NotificationService notifications;

    public CheckoutFacade(InventoryService inventory, PaymentService payment,
                          ShippingService shipping, NotificationService notifications) {
        this.inventory = inventory;
        this.payment = payment;
        this.shipping = shipping;
        this.notifications = notifications;
    }

    // One method hides the complexity of checkout
    public OrderResult checkout(Cart cart, PaymentInfo paymentInfo) {
        inventory.reserve(cart.getItems());
        PaymentResult payResult = payment.charge(paymentInfo, cart.getTotal());
        ShipmentTracking tracking = shipping.createShipment(cart);
        notifications.sendConfirmation(cart.getUserEmail(), tracking);
        return new OrderResult(payResult.getTransactionId(), tracking.getId());
    }
}

When to Use Facade

Scenario Example
Simplify complex library APIs Video conversion, PDF generation
Decouple client from subsystem Checkout process hiding 5 services
Provide default configuration Spring Boot auto-configuration
Layer boundaries Service layer facade over repositories
Legacy system wrapping Clean API over messy legacy code

Q6: What is the Flyweight Pattern?

Answer:

The Flyweight pattern reduces memory usage by sharing common state across many objects instead of storing it in each instance. It splits object state into intrinsic (shared) and extrinsic (unique per context) parts.

graph TD
    linkStyle default stroke:#000,color:#000
    FACTORY["Flyweight Factory<br/>(caches shared objects)"]
    FACTORY --> FW1["Flyweight 'A'<br/>(intrinsic: font, size, color)"]
    FACTORY --> FW2["Flyweight 'B'"]
    FACTORY --> FW3["Flyweight 'C'"]

    C1["Context 1<br/>(extrinsic: position x=10, y=20)"]
    C2["Context 2<br/>(extrinsic: position x=50, y=80)"]
    C3["Context 3<br/>(extrinsic: position x=30, y=60)"]

    C1 --> FW1
    C2 --> FW1
    C3 --> FW2

    NOTE["Contexts 1 & 2 share<br/>the same Flyweight"]

    style FACTORY fill:#8fe0bf,stroke:#333,color:#222
    style FW1 fill:#9fddea,stroke:#333,color:#222
    style NOTE fill:#ffe29a,stroke:#333,color:#222

Implementation: Text Editor Character Rendering

from dataclasses import dataclass

# Flyweight — stores intrinsic (shared) state
@dataclass(frozen=True)  # Immutable — safe to share
class CharacterStyle:
    """Shared state: font properties that many characters share."""
    font_family: str
    font_size: int
    is_bold: bool
    is_italic: bool
    color: str

# Flyweight Factory — caches and reuses styles
class StyleFactory:
    _cache: dict[tuple, CharacterStyle] = {}

    @classmethod
    def get_style(
        cls,
        font_family: str = "Arial",
        font_size: int = 12,
        is_bold: bool = False,
        is_italic: bool = False,
        color: str = "black",
    ) -> CharacterStyle:
        key = (font_family, font_size, is_bold, is_italic, color)
        if key not in cls._cache:
            cls._cache[key] = CharacterStyle(*key)
        return cls._cache[key]

    @classmethod
    def cache_size(cls) -> int:
        return len(cls._cache)

# Context — stores extrinsic (unique) state
@dataclass
class Character:
    """Each character has unique position but shares style."""
    char: str
    row: int
    col: int
    style: CharacterStyle  # Reference to shared flyweight

# Usage: 10,000 characters but only a few style objects
document: list[Character] = []

normal = StyleFactory.get_style()  # Cached
bold = StyleFactory.get_style(is_bold=True)  # Cached
heading = StyleFactory.get_style(font_size=24, is_bold=True)  # Cached

# All 'normal' characters share the SAME style object
for i, char in enumerate("This is a long document with many characters..."):
    document.append(Character(char, row=0, col=i, style=normal))

# Bold words share the bold style object
for i, char in enumerate("IMPORTANT"):
    document.append(Character(char, row=1, col=i, style=bold))

print(f"Characters: {len(document)}")  # 56
print(f"Unique styles: {StyleFactory.cache_size()}")  # 3 (not 56!)

# Memory saved: 53 fewer CharacterStyle objects

Java: String Pool as Flyweight

// Java's String pool IS the Flyweight pattern!
String a = "hello";  // From string pool
String b = "hello";  // Same object from pool
System.out.println(a == b);  // true — same reference

// Integer cache is also Flyweight
Integer x = Integer.valueOf(127);  // Cached (-128 to 127)
Integer y = Integer.valueOf(127);
System.out.println(x == y);  // true — same object from cache

// Boolean.TRUE, Boolean.FALSE — only 2 instances ever

When to Use Flyweight

Use When Don’t Use When
Millions of similar objects Few objects in memory
Objects share most of their state Each object is unique
Memory is a bottleneck CPU is the bottleneck
Immutable shared state Shared state needs modification
Game particles, text characters, map tiles Business entities with distinct state

Q7: What is the Mediator Pattern?

Answer:

The Mediator pattern reduces chaotic dependencies between objects by forcing them to communicate only through a mediator object. Instead of N objects knowing about each other (N² connections), they all talk through one mediator (N connections).

graph TD
    linkStyle default stroke:#000,color:#000
    subgraph Without["Without Mediator (N² connections)"]
        A1["Button"] <--> B1["TextBox"]
        A1 <--> C1["Dropdown"]
        B1 <--> C1
        A1 <--> D1["Checkbox"]
        B1 <--> D1
        C1 <--> D1
    end

    subgraph With["With Mediator (N connections)"]
        M["Mediator<br/>(Dialog)"]
        A2["Button"] --> M
        B2["TextBox"] --> M
        C2["Dropdown"] --> M
        D2["Checkbox"] --> M
        M --> A2
        M --> B2
        M --> C2
        M --> D2
    end

    style Without fill:#ff9f84,stroke:#333,color:#222
    style With fill:#8fe0bf,stroke:#333,color:#222
    style M fill:#9fddea,stroke:#333,color:#222

Implementation: Chat Room

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime

# Mediator interface
class ChatMediator(ABC):
    @abstractmethod
    def send_message(self, message: str, sender: "User") -> None: ...

    @abstractmethod
    def add_user(self, user: "User") -> None: ...

# Concrete mediator
class ChatRoom(ChatMediator):
    def __init__(self, name: str):
        self.name = name
        self._users: list["User"] = []

    def add_user(self, user: "User") -> None:
        self._users.append(user)
        user.chat_room = self
        self.send_message(f"{user.name} joined the room", user)

    def send_message(self, message: str, sender: "User") -> None:
        timestamp = datetime.now().strftime("%H:%M")
        for user in self._users:
            if user != sender:  # Don't echo to sender
                user.receive(f"[{timestamp}] {sender.name}: {message}")

# Colleague
@dataclass
class User:
    name: str
    chat_room: ChatMediator | None = None
    messages: list[str] = field(default_factory=list)

    def send(self, message: str) -> None:
        if self.chat_room:
            self.chat_room.send_message(message, self)

    def receive(self, message: str) -> None:
        self.messages.append(message)
        print(f"  {self.name} received: {message}")

# Usage — users don't know about each other, only the mediator
room = ChatRoom("Engineering")

alice = User("Alice")
bob = User("Bob")
charlie = User("Charlie")

room.add_user(alice)
room.add_user(bob)
room.add_user(charlie)

alice.send("Hey team, standup in 5 minutes!")
# Bob received: [10:00] Alice: Hey team, standup in 5 minutes!
# Charlie received: [10:00] Alice: Hey team, standup in 5 minutes!

bob.send("On my way!")
# Alice received: [10:00] Bob: On my way!
# Charlie received: [10:00] Bob: On my way!

Mediator in Enterprise Systems

// Spring's ApplicationEventPublisher is a Mediator!
@Service
public class OrderService {

    @Autowired
    private ApplicationEventPublisher eventPublisher; // Mediator

    public Order createOrder(OrderRequest request) {
        Order order = orderRepo.save(new Order(request));
        // Publish event — OrderService doesn't know who handles it
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
        return order;
    }
}

// Listeners are decoupled — they don't know about each other
@Component
public class InventoryListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        inventoryService.reserve(event.getOrder().getItems());
    }
}

@Component
public class NotificationListener {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        emailService.sendConfirmation(event.getOrder());
    }
}

Mediator vs Observer

Aspect Mediator Observer
Communication Bidirectional through mediator One-way (subject → observers)
Coupling Components know the mediator Observers don’t know each other
Complexity Mediator can become complex (God object) Simpler, distributed logic
Example Chat room, air traffic control Event bus, pub/sub

Q8: What is the Repository Pattern?

Answer:

The Repository pattern mediates between the domain/business layer and data access, providing a collection-like interface for accessing domain objects. It isolates the domain from persistence concerns and is a cornerstone of both Spring Data and Domain-Driven Design.

graph TD
    linkStyle default stroke:#000,color:#000
    SERVICE["Service Layer<br/>(business logic)"]
    SERVICE --> REPO["Repository Interface<br/>findById(), save(), delete()"]
    REPO --> IMPL1["JPA Implementation"]
    REPO --> IMPL2["MongoDB Implementation"]
    REPO --> IMPL3["In-Memory (tests)"]

    IMPL1 --> DB1["PostgreSQL"]
    IMPL2 --> DB2["MongoDB"]
    IMPL3 --> DB3["HashMap"]

    style REPO fill:#8fe0bf,stroke:#333,color:#222
    style SERVICE fill:#9fddea,stroke:#333,color:#222

Spring Data JPA Repository

// Spring Data auto-generates the implementation!
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // Method name IS the query — Spring generates SQL
    List<User> findByEmail(String email);

    List<User> findByRoleAndActiveTrue(String role);

    @Query("SELECT u FROM User u WHERE u.createdAt > :since")
    List<User> findRecentUsers(@Param("since") LocalDateTime since);

    // Pagination built in
    Page<User> findByRole(String role, Pageable pageable);
}

// Entity
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String name;

    private String role;
    private boolean active;
    private LocalDateTime createdAt;

    // Getters, setters, constructors...
}

// Service uses repository — doesn't know about JPA/SQL
@Service
public class UserService {

    private final UserRepository userRepo;

    public UserService(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    public User createUser(CreateUserRequest request) {
        if (!userRepo.findByEmail(request.getEmail()).isEmpty()) {
            throw new DuplicateEmailException(request.getEmail());
        }
        User user = new User(request.getName(), request.getEmail());
        return userRepo.save(user);
    }

    public Page<User> listAdmins(int page, int size) {
        return userRepo.findByRole("admin", PageRequest.of(page, size));
    }
}

Python Repository Pattern

from typing import Protocol, TypeVar, Generic
from dataclasses import dataclass

T = TypeVar("T")

# Generic repository interface
class Repository(Protocol[T]):
    def get_by_id(self, id: int) -> T | None: ...
    def list_all(self) -> list[T]: ...
    def save(self, entity: T) -> T: ...
    def delete(self, id: int) -> bool: ...

# Domain entity
@dataclass
class User:
    id: int | None
    name: str
    email: str
    role: str = "user"

# PostgreSQL implementation
class PostgresUserRepository:
    def __init__(self, db_pool):
        self._pool = db_pool

    async def get_by_id(self, id: int) -> User | None:
        row = await self._pool.fetchrow(
            "SELECT * FROM users WHERE id = $1", id
        )
        return User(**dict(row)) if row else None

    async def list_all(self) -> list[User]:
        rows = await self._pool.fetch("SELECT * FROM users")
        return [User(**dict(r)) for r in rows]

    async def save(self, user: User) -> User:
        if user.id is None:
            row = await self._pool.fetchrow(
                "INSERT INTO users (name, email, role) VALUES ($1, $2, $3) RETURNING id",
                user.name, user.email, user.role,
            )
            user.id = row["id"]
        else:
            await self._pool.execute(
                "UPDATE users SET name=$1, email=$2, role=$3 WHERE id=$4",
                user.name, user.email, user.role, user.id,
            )
        return user

    async def delete(self, id: int) -> bool:
        result = await self._pool.execute(
            "DELETE FROM users WHERE id = $1", id
        )
        return result == "DELETE 1"

# In-memory implementation (for testing)
class InMemoryUserRepository:
    def __init__(self):
        self._store: dict[int, User] = {}
        self._next_id = 1

    async def get_by_id(self, id: int) -> User | None:
        return self._store.get(id)

    async def list_all(self) -> list[User]:
        return list(self._store.values())

    async def save(self, user: User) -> User:
        if user.id is None:
            user.id = self._next_id
            self._next_id += 1
        self._store[user.id] = user
        return user

    async def delete(self, id: int) -> bool:
        return self._store.pop(id, None) is not None

# Service works with either implementation
class UserService:
    def __init__(self, repo: Repository[User]):
        self._repo = repo

Repository vs DAO vs Active Record

Pattern Description Example
Repository Domain-oriented collection interface Spring Data, custom repos
DAO Table-oriented data access JDBC DAOs, lower-level
Active Record Entity knows how to persist itself Django ORM, Rails
Data Mapper Separate mapper objects SQLAlchemy (mapper mode)

Q9: What is the MVC Pattern and how is it implemented in Python web frameworks?

Answer:

Model-View-Controller (MVC) separates an application into three concerns: the Model (data + business logic), the View (presentation), and the Controller (handles input and coordinates model/view).

graph TD
    linkStyle default stroke:#000,color:#000
    USER["User (Browser)"]
    USER -->|"HTTP Request"| CONTROLLER["Controller<br/>(handles input)"]
    CONTROLLER -->|"calls"| MODEL["Model / Service<br/>(business logic)"]
    MODEL -->|"returns data"| CONTROLLER
    CONTROLLER -->|"selects + populates"| VIEW["View<br/>(renders response)"]
    VIEW -->|"HTTP Response"| USER

    subgraph Spring["Spring MVC"]
        DS["DispatcherServlet<br/>(Front Controller)"]
        DS --> HM["Handler Mapping<br/>(route → controller)"]
        DS --> HA["Handler Adapter<br/>(invoke controller)"]
        DS --> VR["View Resolver<br/>(template → HTML)"]
    end

    style CONTROLLER fill:#8fe0bf,stroke:#333,color:#222
    style MODEL fill:#9fddea,stroke:#333,color:#222
    style VIEW fill:#ffe29a,stroke:#333,color:#222
    style DS fill:#ff9f84,stroke:#333,color:#222
    style Spring fill:#fff

Python MVC Implementation (FastAPI)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

# MODEL
class Product(BaseModel):
    id: int | None = None
    name: str
    price: float
    stock: int

class ProductService:
    def __init__(self):
        self._products: dict[int, Product] = {}
        self._next_id = 1

    def list_all(self) -> list[Product]:
        return list(self._products.values())

    def get_by_id(self, product_id: int) -> Product:
        product = self._products.get(product_id)
        if product is None:
            raise KeyError(f"Product {product_id} not found")
        return product

    def create(self, product: Product) -> Product:
        product.id = self._next_id
        self._products[self._next_id] = product
        self._next_id += 1
        return product

service = ProductService()

# CONTROLLER (JSON endpoints)
@app.get("/api/v1/products", response_model=list[Product])
def list_products():
    return service.list_all()

@app.get("/api/v1/products/{product_id}", response_model=Product)
def get_product(product_id: int):
    try:
        return service.get_by_id(product_id)
    except KeyError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc

@app.post("/api/v1/products", response_model=Product, status_code=201)
def create_product(product: Product):
    return service.create(product)

MVC Variants

Variant View Layer Controller Returns Example
MVC (classic) Server-side templates View name + Model Spring MVC + Thymeleaf
REST API (no view) Client (React, mobile) JSON directly @RestController
MVP Passive view Updates view directly Android (legacy)
MVVM Data-bound view ViewModel + bindings WPF, Vue.js, SwiftUI

Q10: What is the CQRS Pattern?

Answer:

Command Query Responsibility Segregation (CQRS) splits the data model into separate command (write) and query (read) models. Writes and reads often have different requirements — CQRS lets you optimize each independently.

graph TD
    linkStyle default stroke:#000,color:#000
    CLIENT["Client"]
    CLIENT -->|"Write (Command)"| CMD["Command Handler<br/>(validates, applies rules)"]
    CLIENT -->|"Read (Query)"| QUERY["Query Handler<br/>(optimized for reads)"]

    CMD --> WRITE_DB["Write Model<br/>(normalized, consistent)"]
    QUERY --> READ_DB["Read Model<br/>(denormalized, fast)"]

    WRITE_DB -->|"Events / Sync"| READ_DB

    style CMD fill:#ff9f84,stroke:#333,color:#222
    style QUERY fill:#8fe0bf,stroke:#333,color:#222
    style WRITE_DB fill:#9fddea,stroke:#333,color:#222
    style READ_DB fill:#ffe29a,stroke:#333,color:#222

Implementation

from dataclasses import dataclass, field
from datetime import datetime
from abc import ABC, abstractmethod

# COMMANDS (writes)
@dataclass(frozen=True)
class CreateOrderCommand:
    user_id: int
    items: list[dict]
    shipping_address: str

@dataclass(frozen=True)
class CancelOrderCommand:
    order_id: int
    reason: str

# Command handler — enforces business rules
class OrderCommandHandler:
    def __init__(self, write_repo, event_bus):
        self._repo = write_repo
        self._events = event_bus

    def handle_create(self, cmd: CreateOrderCommand) -> int:
        # Validate business rules
        if not cmd.items:
            raise ValueError("Order must have at least one item")

        # Create in write model (normalized)
        order = Order(
            user_id=cmd.user_id,
            items=cmd.items,
            address=cmd.shipping_address,
            status="pending",
            created_at=datetime.now(),
        )
        self._repo.save(order)

        # Publish event to sync read model
        self._events.publish("order_created", order_id=order.id, data=order.to_dict())
        return order.id

    def handle_cancel(self, cmd: CancelOrderCommand) -> None:
        order = self._repo.get(cmd.order_id)
        if order.status == "shipped":
            raise ValueError("Cannot cancel shipped order")
        order.status = "cancelled"
        order.cancel_reason = cmd.reason
        self._repo.save(order)
        self._events.publish("order_cancelled", order_id=order.id)

# QUERIES (reads)
@dataclass(frozen=True)
class GetOrderQuery:
    order_id: int

@dataclass(frozen=True)
class ListUserOrdersQuery:
    user_id: int
    page: int = 1
    page_size: int = 20

# Query handler — optimized for read performance
class OrderQueryHandler:
    def __init__(self, read_repo):
        self._repo = read_repo  # Denormalized read model

    def handle_get(self, query: GetOrderQuery) -> dict:
        # Read from denormalized view (fast, no JOINs)
        return self._repo.get_order_view(query.order_id)

    def handle_list(self, query: ListUserOrdersQuery) -> dict:
        orders, total = self._repo.list_user_orders(
            query.user_id, query.page, query.page_size
        )
        return {"items": orders, "total": total, "page": query.page}

# Read model sync (event listener)
class OrderReadModelUpdater:
    """Listens to write events and updates the denormalized read model."""

    def __init__(self, read_repo):
        self._repo = read_repo

    def on_order_created(self, event: dict) -> None:
        # Denormalize: pre-join user name, item details, totals
        self._repo.upsert_order_view({
            "order_id": event["order_id"],
            "user_name": user_service.get_name(event["data"]["user_id"]),
            "items_summary": summarize_items(event["data"]["items"]),
            "total": calculate_total(event["data"]["items"]),
            "status": "pending",
            "created_at": event["data"]["created_at"],
        })

# API layer
@app.post("/api/v1/orders", status_code=201)
async def create_order(cmd: CreateOrderCommand):
    order_id = command_handler.handle_create(cmd)
    return {"order_id": order_id}

@app.get("/api/v1/orders/{order_id}")
async def get_order(order_id: int):
    return query_handler.handle_get(GetOrderQuery(order_id))

Python CQRS with Separate Models

from sqlalchemy import DateTime, Integer, String, Numeric
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from datetime import datetime

class Base(DeclarativeBase):
    pass

# Write model — normalized, validated
class Order(Base):
    __tablename__ = "orders"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(Integer, nullable=False)
    status: Mapped[str] = mapped_column(String(32), default="pending")
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

# Read model — denormalized for fast queries
class OrderView(Base):
    __tablename__ = "order_views"

    order_id: Mapped[int] = mapped_column(Integer, primary_key=True)
    user_name: Mapped[str] = mapped_column(String(255), nullable=False)
    items_summary: Mapped[str] = mapped_column(String(1024), nullable=False)
    total: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
    status: Mapped[str] = mapped_column(String(32), nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)

# Repositories are intentionally separate (write repo vs read repo)
class OrderRepository:
    def __init__(self, session):
        self.session = session

class OrderViewRepository:
    def __init__(self, session):
        self.session = session

    def find_by_user_name(self, name: str, limit: int = 20, offset: int = 0):
        stmt = (
            self.session.query(OrderView)
            .filter(OrderView.user_name.ilike(f"%{name}%"))
            .offset(offset)
            .limit(limit)
        )
        return stmt.all()

When to Use CQRS

Use CQRS Don’t Use CQRS
Read and write workloads differ significantly Simple CRUD applications
Read model needs denormalization Reads and writes have same shape
Scaling reads and writes independently Small teams / simple domains
Event sourcing architecture When eventual consistency is unacceptable
Complex reporting / search requirements When added complexity isn’t justified

Summary Table

# Pattern Category Key Idea
1 Python DI IoC / Creational Dependencies injected via constructors, provider functions, or Depends()
2 Inversion of Control Principle Framework controls object creation and flow
3 Template Method Behavioral Algorithm skeleton in base class, steps in subclasses
4 State Behavioral Object behavior changes with internal state transitions
5 Facade Structural Simplified interface to a complex subsystem
6 Flyweight Structural Share common state across many objects to save memory
7 Mediator Behavioral Central hub reduces N² dependencies to N
8 Repository Data Access Collection-like interface isolating domain from persistence
9 MVC Architectural Separate Model, View, Controller concerns
10 CQRS Architectural Separate read and write models for independent optimization

What’s Next?

This article covered enterprise and framework-level patterns. For related content:

TipEnjoyed this article?

If this article helped you, your support helps us deliver more useful content. Here are a few ways to support our work: