Object-Oriented Design Patterns for Coding Interviews

Design patterns appear in coding interviews in two ways: the interviewer asks you to implement a specific pattern, or you’re expected to recognize that a pattern applies to the problem and mention it naturally. Knowing the six patterns below covers the vast majority of what comes up.

Strategy Pattern

Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable. The client selects a strategy at runtime without knowing its implementation.

# Python
class Sorter:
    def __init__(self, strategy):
        self._strategy = strategy

    def sort(self, data):
        return self._strategy.sort(data)

class QuickSort:
    def sort(self, data): ...

class MergeSort:
    def sort(self, data): ...

sorter = Sorter(QuickSort())
sorter.sort([3, 1, 2])

When to use: multiple algorithms for the same behavior (sorting, pricing, routing); want to swap implementations at runtime or per-config without branching logic. Also appears as the payment processor pattern: PaymentStrategy with StripeStrategy and PayPalStrategy implementations.

Observer Pattern

Intent: Define a one-to-many dependency so that when one object changes state, all its dependents are notified automatically.

# Python
class EventBus:
    def __init__(self):
        self._subscribers = defaultdict(list)

    def subscribe(self, event_type, handler):
        self._subscribers[event_type].append(handler)

    def publish(self, event_type, data):
        for handler in self._subscribers[event_type]:
            handler(data)

bus = EventBus()
bus.subscribe("price_change", send_alert_email)
bus.subscribe("price_change", update_dashboard)
bus.publish("price_change", {"ticker": "AAPL", "price": 189.50})

Push vs pull: in push mode the subject sends the new value to observers (shown above). In pull mode the subject sends a notification and observers fetch the data themselves — useful when observers only need a subset of the data. In system design interviews, Observer is the conceptual basis for event-driven architectures (Kafka topics, webhook callbacks, UI reactivity).

Factory Pattern

Factory Method: define an interface for creating an object, but let subclasses decide which class to instantiate.

# Java-style pseudocode
abstract class NotificationSender {
    abstract Notification createNotification(String msg);

    void send(String msg) {
        Notification n = createNotification(msg);
        n.deliver();
    }
}

class EmailSender extends NotificationSender {
    Notification createNotification(String msg) {
        return new EmailNotification(msg);
    }
}

Abstract Factory: creates families of related objects. Example: a UIFactory that produces Button, Checkbox, and Dialog — with a DarkThemeFactory and LightThemeFactory as concrete implementations. Use Abstract Factory when the products must be consistent with each other.

Singleton Pattern

Intent: Ensure a class has only one instance and provide a global access point to it.

# Python thread-safe (double-checked locking equivalent via module-level)
class ConfigManager:
    _instance = None
    _lock = threading.Lock()

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:          # second check inside lock
                    cls._instance = cls()
        return cls._instance

When NOT to use: Singletons introduce hidden global state that makes unit testing hard — you can’t inject a mock. For most cases, create one instance at application startup and pass it via dependency injection instead. Mention this trade-off in interviews; it signals maturity.

Decorator Pattern

Intent: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

# Python — wrapping an HTTP client with logging and retry
class HttpClient:
    def get(self, url): ...

class LoggingDecorator:
    def __init__(self, client):
        self._client = client

    def get(self, url):
        print(f"GET {url}")
        response = self._client.get(url)
        print(f"Response: {response.status}")
        return response

class RetryDecorator:
    def __init__(self, client, retries=3):
        self._client = client
        self._retries = retries

    def get(self, url):
        for attempt in range(self._retries):
            try:
                return self._client.get(url)
            except Exception:
                if attempt == self._retries - 1:
                    raise

client = RetryDecorator(LoggingDecorator(HttpClient()))

Java’s InputStream/BufferedInputStream/GZIPInputStream chain is the textbook example. In web frameworks, middleware stacks are a direct application of Decorator.

Command Pattern

Intent: Encapsulate a request as an object, thereby letting you parameterize clients, queue operations, log them, and support undoable operations.

class Command:
    def execute(self): ...
    def undo(self):    ...

class InsertTextCommand(Command):
    def __init__(self, doc, position, text):
        self.doc = doc
        self.position = position
        self.text = text

    def execute(self):
        self.doc.insert(self.position, self.text)

    def undo(self):
        self.doc.delete(self.position, len(self.text))

class Editor:
    def __init__(self):
        self.history = []

    def run(self, cmd):
        cmd.execute()
        self.history.append(cmd)

    def undo(self):
        if self.history:
            self.history.pop().undo()

Command appears in system design when discussing collaborative document editors (like Google Docs): each keystroke is a Command object that can be applied, reversed, or transmitted to other clients for operational transform.

Patterns in System Design Interviews

  • Strategy: mention when designing a dispatch system, ranking algorithm, or payment flow — “we can abstract the routing logic behind a Strategy interface so we can swap algorithms without touching the core service.”
  • Observer: use when explaining event-driven architecture — “downstream services subscribe to order events via Kafka; this is the Observer pattern at the infrastructure level.”
  • Command: invoke when discussing undo/redo (collaborative editors) or audit logs — “every mutation is a Command object persisted to an event log, which gives us replay and rollback for free.”

Meta coding interviews test OOP design and design patterns. See common questions for Meta interview: object-oriented design and design patterns.

Atlassian coding rounds include OOP design and design patterns. Review patterns for Atlassian interview: design patterns and OOP system design.

Apple coding interviews test design patterns and OOP principles. See patterns for Apple interview: design patterns and object-oriented design.

Scroll to Top