# Coding Interview: OOP Design Patterns — Strategy, Observer, Factory, Singleton, Decorator, Adapter, SOLID

Source: https://www.techinterview.org/post/3233474138/coding-interview-oop-design-patterns-strategy-observer-factory-singleton-decorator-adapter-solid-principles/
Updated: 2026-07-11 · techinterview.org

Object-oriented design questions test your ability to model real-world systems using classes, interfaces, and design patterns. These questions appear frequently in [interviews](/category/interview-prep/) at Amazon, Google, and Microsoft, especially for senior roles. This guide covers the design patterns most commonly tested, the SOLID principles that guide good design, and how to approach OOP design questions systematically.

## SOLID Principles

SOLID is the foundation of good object-oriented design. (1) Single Responsibility Principle (SRP) -- a class should have one reason to change. A UserService that handles authentication, profile updates, and email notifications violates SRP. Split into AuthService, ProfileService, and NotificationService. (2) Open/Closed Principle (OCP) -- classes should be open for extension but closed for modification. Add new behavior by creating new classes (subclasses, strategy implementations), not by modifying existing code. (3) Liskov Substitution Principle (LSP) -- subtypes must be substitutable for their base types. If a function accepts a Shape, it should work correctly with any Shape subclass (Circle, Rectangle). A Square that inherits from Rectangle but changes both width and height when one is set violates LSP. (4) Interface Segregation Principle (ISP) -- clients should not be forced to depend on interfaces they do not use. Split a large IWorker interface (work, eat, sleep) into IWorkable and IFeedable -- a robot implements IWorkable but not IFeedable. (5) Dependency Inversion Principle (DIP) -- depend on abstractions, not concretions. A PaymentProcessor should depend on a PaymentGateway interface, not on a concrete StripeGateway class. This allows swapping Stripe for PayPal without modifying PaymentProcessor.

## Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Use when: you have multiple ways to perform an operation and want to select the algorithm at runtime. Example: a navigation app that supports different routing strategies -- shortest distance, fastest time, avoid highways. Define a RouteStrategy interface with a method calculateRoute(origin, destination). Implement ShortestDistanceStrategy, FastestTimeStrategy, and AvoidHighwaysStrategy. The Navigator class holds a reference to a RouteStrategy and delegates route calculation to it. The user selects a strategy, and the Navigator uses it without knowing the implementation details. Interview application: "Design a [payment system](/category/system-design/) that supports multiple payment methods." Define a PaymentStrategy interface with process(amount). Implement CreditCardPayment, PayPalPayment, CryptoPayment. The Checkout class uses whichever strategy the user selects. Adding a new payment method requires only a new strategy class -- no modification to Checkout (Open/Closed Principle).

## Observer Pattern

The Observer pattern defines a one-to-many dependency between objects: when one object (the subject) changes state, all its dependents (observers) are notified automatically. Use when: multiple components need to react to state changes without tight coupling. Example: a stock price ticker. The StockMarket (subject) maintains a list of observers. When a stock price changes, it calls notify() which iterates through all observers and calls their update(stock, price) method. Observers include: a PriceAlertService (sends alerts when price crosses a threshold), a PortfolioTracker (updates portfolio value), and a ChartRenderer (updates the price chart). Adding a new observer requires no changes to StockMarket -- just implement the Observer interface and register. Interview application: "Design a [notification system](/post/3233474168/system-design-twitter-news-feed-timeline-fanout-on-write-fanout-on-read-celebrity-problem-ranking-caching/) for an e-commerce platform." Events (OrderPlaced, PaymentFailed, ItemShipped) are subjects. Observers include: EmailNotifier, SMSNotifier, PushNotifier, AnalyticsTracker. Each observer decides independently whether and how to handle each event. The event system is decoupled from the notification channels.

## Factory and Abstract Factory Patterns

The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate. Use when: the exact type of object to create depends on runtime conditions. Simple Factory: a static method that returns different types based on input. NotificationFactory.create("email") returns an EmailNotification; create("sms") returns SMSNotification. This centralizes object creation and hides implementation details. Factory Method: an abstract class defines a factory method that subclasses override. A DocumentCreator has an abstract createDocument() method. WordDocumentCreator returns a WordDocument; PDFDocumentCreator returns a PDFDocument. Each creator knows how to construct its specific document type. Abstract Factory: a factory of factories. A UIFactory creates related families of UI components: WindowsUIFactory creates WindowsButton, WindowsCheckbox, WindowsTextfield. MacUIFactory creates MacButton, MacCheckbox, MacTextfield. The application uses the factory interface and works with any platform without knowing the concrete classes. Interview tip: when you need object creation that varies by type, reach for a factory. It keeps the creation logic in one place and follows the Open/Closed Principle.

## Decorator Pattern

The Decorator pattern attaches additional behavior to an object dynamically without altering its interface. Use when: you need to add responsibilities to individual objects, not to an entire class, and you want to combine behaviors flexibly. Classic example: a coffee ordering system. A base Coffee class with cost() and description(). Decorators: MilkDecorator, SugarDecorator, WhipCreamDecorator. Each wraps a Coffee object and adds to its cost and description. new WhipCreamDecorator(new MilkDecorator(new Espresso())) creates an espresso with milk and whip cream. Cost is computed by chaining: whip cream cost + milk cost + espresso cost. This avoids a class explosion (EspressoWithMilk, EspressoWithMilkAndSugar, ...). Real-world usage: Java I/O streams (BufferedInputStream wraps FileInputStream wraps InputStream), middleware chains in web frameworks (logging middleware wraps authentication middleware wraps the handler). Interview application: "Design a text formatting system." Base: PlainText. Decorators: BoldDecorator, ItalicDecorator, UnderlineDecorator. Each wraps the text and adds formatting tags. Decorators can be combined in any order.

## Singleton Pattern and Its Problems

The Singleton pattern ensures a class has only one instance and provides a global access point. Use cases: database connection pools, configuration managers, logging services, thread pools. Implementation: a private constructor, a private static instance variable, and a public static getInstance() method. Thread-safe implementation: use double-checked locking or initialize the instance in a static block (eager initialization). Problems with Singleton: (1) Global state -- singletons are effectively global variables, making it hard to reason about state and dependencies. (2) Testing difficulty -- singletons cannot be easily mocked or replaced in tests. Code that calls DatabaseConnection.getInstance() is tightly coupled to the concrete class. (3) Hidden dependencies -- a class that uses a singleton internally has an invisible dependency that is not expressed in its constructor or interface. Better alternative: dependency injection. Instead of a class creating or fetching its dependencies, inject them through the constructor. The DI container (Spring, Guice) manages the lifecycle and ensures single instances where needed. In interviews: know the Singleton pattern and its implementation, but acknowledge its drawbacks and suggest DI as the modern alternative.

## Approaching OOP Design Questions in Interviews

Step-by-step framework: (1) Clarify requirements -- what are the core use cases? For "design a parking lot": how many levels, what vehicle types, what operations (park, leave, find available spot)? (2) Identify the key objects -- nouns in the requirements become classes. Parking lot, level, parking spot, vehicle, ticket. (3) Define relationships -- a ParkingLot has many Levels. A Level has many ParkingSpots. A ParkingSpot can hold one Vehicle. A Ticket links a Vehicle to a ParkingSpot with entry time. (4) Define interfaces and methods -- ParkingLot: parkVehicle(vehicle), leaveVehicle(ticket), getAvailableSpots(vehicleType). ParkingSpot: isAvailable(), park(vehicle), removeVehicle(). (5) Apply design patterns where appropriate -- Strategy pattern for different parking fee calculations (hourly, daily, monthly). Observer pattern to notify display boards when spot availability changes. Factory pattern to create different vehicle types. (6) Consider extensibility -- what if we add electric vehicle charging spots? Subclass ParkingSpot as ElectricParkingSpot with a startCharging() method. The design should accommodate new features without modifying existing classes.
