I failed my first LLD interview at a product company in 2021. The question was "Design a Parking Lot System." I knew OOP. I knew classes and interfaces. But when the interviewer asked me to make the pricing strategy extensible without modifying existing code, I froze. I had no framework for thinking about these problems.

Then I learned design patterns. Not all 23 from the Gang of Four book. Just the ones that actually show up in interviews. Everything clicked.

Welcome to Grind Engineer, your guide to becoming a better software engineer! No fluff. Pure engineering insights.

TL;DR: You do not need all 23 GoF patterns for LLD interviews. 12 patterns cover virtually every question you will face at FAANG and top product companies. This article breaks down each one with the interview question where it appears, the problem it solves, and a code example you can use tomorrow.

Job Opening

The Big 4 (learn these first, they cover 80% of interviews)

1. Strategy Pattern

What it solves: You have multiple algorithms for the same task, and the right one depends on context.

Interview trigger: Anytime you see if (type == "X") or a switch statement choosing behavior based on type, that is a Strategy pattern waiting to happen.

# Without Strategy (ugly switch)
def calculate_fare(ride_type, distance):
    if ride_type == "pool":
        return distance * 5
    elif ride_type == "go":
        return distance * 10
    elif ride_type == "premier":
        return distance * 20

# With Strategy (clean, extensible)
class FareStrategy:
    def calculate(self, distance): pass

class PoolFare(FareStrategy):
    def calculate(self, distance): return distance * 5

class PremierFare(FareStrategy):
    def calculate(self, distance): return distance * 20

class Ride:
    def __init__(self, strategy: FareStrategy):
        self.strategy = strategy

    def fare(self, distance):
        return self.strategy.calculate(distance)

Where it appears: Parking Lot (pricing strategies), Chess (piece movement strategies), Payment System (credit card vs UPI vs wallet), Ride Sharing (fare calculation).

2. Observer Pattern

What it solves: When one object changes state, multiple other objects need to know about it, but you do not want them tightly coupled.

Interview trigger: Anytime the words "notify," "subscribe," "alert," or "update" appear in requirements.

class EventManager:
    def __init__(self):
        self.listeners = {}

    def subscribe(self, event_type, listener):
        self.listeners.setdefault(event_type, []).append(listener)

    def notify(self, event_type, data):
        for listener in self.listeners.get(event_type, []):
            listener.update(data)

# Usage: Stock price changes notify Dashboard, AlertService, Logger

Where it appears: Stock Ticker (price updates), Notification System, Auction System (bid updates), Social Media Feed (new post alerts), Library System (book available alerts).

3. Factory Pattern

What it solves: Object creation logic is complex or depends on runtime input, and you want to centralize it instead of scattering new calls everywhere.

Interview trigger: When you need to create different types of objects based on input, especially when the exact class is determined at runtime.

class VehicleFactory:
    @staticmethod
    def create(vehicle_type: str):
        if vehicle_type == "car":
            return Car()
        elif vehicle_type == "truck":
            return Truck()
        elif vehicle_type == "bike":
            return Bike()
        raise ValueError(f"Unknown type: {vehicle_type}")

# Caller does not need to know which class to instantiate
vehicle = VehicleFactory.create("car")

Where it appears: Parking Lot (vehicle types), Logger (file logger vs console logger vs DB logger), Document Editor (creating different document types), Game (enemy/character creation).

4. State Pattern

What it solves: An object's behavior changes based on its internal state, and you want to avoid massive if/else chains checking the current state.

Interview trigger: Anytime you see state transitions: "pending → confirmed → shipped → delivered" or "idle → dispensing → out of stock."

💡 Key Insight: If you only learn four patterns for LLD interviews, make them Strategy, Observer, Factory, and State. Multiple interview prep resources confirm these four cover roughly 90% of all LLD questions across FAANG companies.

class VendingMachineState:
    def insert_coin(self, machine): pass
    def select_item(self, machine): pass
    def dispense(self, machine): pass

class IdleState(VendingMachineState):
    def insert_coin(self, machine):
        print("Coin accepted")
        machine.set_state(HasCoinState())

class HasCoinState(VendingMachineState):
    def select_item(self, machine):
        print("Item selected, dispensing...")
        machine.set_state(DispensingState())

Where it appears: Vending Machine, Elevator System, Order Management (order lifecycle), ATM, Traffic Light Controller, Document Workflow (draft → review → published).

The Supporting 8 (learn these next, they complete the toolkit)

5. Singleton Pattern

What it solves: Ensures exactly one instance of a class exists globally.

Use carefully. Singleton is the most famous pattern but also the most overused. It introduces global state, makes testing harder, and hides dependencies. Use it only for things like configuration managers, connection pools, or loggers where a single instance genuinely makes sense.

class DatabaseConnection:
    _instance = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

Where it appears: Database connection pool, Configuration manager, Logger instance.

6. Decorator Pattern

What it solves: Adding behavior to an object dynamically without modifying its class. Like wrapping a gift: the gift stays the same, but each wrapper adds something.

class Coffee:
    def cost(self): return 5

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    def cost(self): return self.coffee.cost() + 2

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    def cost(self): return self.coffee.cost() + 1

# Stack decorators: Coffee + Milk + Sugar = 5 + 2 + 1 = 8
drink = SugarDecorator(MilkDecorator(Coffee()))

Where it appears: Pizza/Coffee ordering (toppings), Stream processing (buffered + encrypted + compressed stream), Notification channels (email + SMS + push).

7. Builder Pattern

What it solves: Constructing complex objects step by step, especially when the constructor would need too many parameters.

class QueryBuilder:
    def __init__(self):
        self._table = None
        self._conditions = []
        self._limit = None

    def table(self, name):
        self._table = name
        return self

    def where(self, condition):
        self._conditions.append(condition)
        return self

    def limit(self, n):
        self._limit = n
        return self

    def build(self):
        query = f"SELECT * FROM {self._table}"
        if self._conditions:
            query += " WHERE " + " AND ".join(self._conditions)
        if self._limit:
            query += f" LIMIT {self._limit}"
        return query

# Clean, readable construction
query = QueryBuilder().table("users").where("age > 25").limit(10).build()

Where it appears: Query builders, Meal/Order builders (restaurant system), Document creation, Complex configuration objects.

8. Adapter Pattern

What it solves: Making two incompatible interfaces work together. The classic "square peg in a round hole" problem.

class OldPaymentGateway:
    def make_payment(self, amount_in_cents):
        print(f"Paid {amount_in_cents} cents")

class ModernPaymentAdapter:
    def __init__(self, old_gateway):
        self.old = old_gateway

    def pay(self, amount_in_dollars):
        self.old.make_payment(int(amount_in_dollars * 100))

Where it appears: Payment gateway integrations (old API to new API), Third party service wrappers, Legacy system migration.

9. Command Pattern

What it solves: Encapsulates a request as an object, allowing you to parameterize, queue, log, or undo operations.

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

class BoldCommand(Command):
    def __init__(self, editor):
        self.editor = editor

    def execute(self):
        self.editor.apply_bold()

    def undo(self):
        self.editor.remove_bold()

# Command history enables Ctrl+Z
history = []
cmd = BoldCommand(editor)
cmd.execute()
history.append(cmd)
# Undo: history.pop().undo()

Where it appears: Text Editor (undo/redo), Remote Control system, Task queue, Transaction management.

10. Iterator Pattern

What it solves: Provides a way to traverse a collection without exposing its internal structure (array, tree, graph, linked list).

Where it appears: Playlist navigation (next/previous), File system traversal, Social media feed pagination. Most languages have this built in (Python's __iter__, Java's Iterator), so you rarely implement from scratch in interviews, but knowing the concept matters.

11. Template Method Pattern

What it solves: Defines the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the overall structure.

class DataProcessor:
    def process(self):       # Template method
        data = self.read()
        cleaned = self.clean(data)
        result = self.analyze(cleaned)
        self.save(result)

    def read(self): pass     # Subclasses override these
    def clean(self, data): pass
    def analyze(self, data): pass
    def save(self, result): pass

class CSVProcessor(DataProcessor):
    def read(self): return "csv data"
    def clean(self, data): return data.strip()
    # ... other steps

Where it appears: Data pipeline processing, Game loop (init → update → render), Report generation (fetch → format → export).

12. Chain of Responsibility Pattern

What it solves: Passes a request through a chain of handlers, where each handler either processes it or forwards it to the next one.

class Handler:
    def __init__(self, next_handler=None):
        self.next = next_handler

    def handle(self, request):
        if self.next:
            self.next.handle(request)

class AuthHandler(Handler):
    def handle(self, request):
        if not request.get("token"):
            return "Unauthorized"
        super().handle(request)

class RateLimitHandler(Handler):
    def handle(self, request):
        if request.get("rate") > 100:
            return "Rate limited"
        super().handle(request)

# Chain: Auth → RateLimit → BusinessLogic
chain = AuthHandler(RateLimitHandler(BusinessLogicHandler()))

Where it appears: Middleware pipelines (auth → rate limit → logging → handler), Logger (different log levels), Approval workflows (manager → director → VP), Exception handling chains.

When to Use What

Pattern

Trigger Phrase in Requirements

Most Common Question

Strategy

"Different algorithms for..."

Parking Lot, Payment System

Observer

"Notify when..."

Stock Ticker, Notification System

Factory

"Create different types of..."

Vehicle types, Document types

State

"Status changes from X to Y"

Vending Machine, Order System

Singleton

"One shared instance of..."

Config Manager, DB Pool

Decorator

"Add features on top of..."

Coffee Shop, Stream Processing

Builder

"Complex object with many options"

Query Builder, Meal Builder

Adapter

"Old system meets new interface"

Payment Gateway Integration

Command

"Undo, queue, or log actions"

Text Editor, Task Queue

Iterator

"Traverse a collection"

Playlist, Feed Pagination

Template Method

"Same steps, different details"

Data Pipeline, Game Loop

Chain of Responsibility

"Pass through multiple checks"

Middleware, Approval Workflow

For more visual explanations of system design, DSA patterns, AI in engineering and tech productivity, Subscribe To Grind Engineer! 🚀

Follow me on Youtube · LinkedIn · X · Instagram to stay updated.

See you in the next one!

Signing Off, Scortier

Subscribe to keep reading

This content is free, but you must be subscribed to Grind Engineer to continue reading.

Already a subscriber?Sign in.Not now

Reply

Avatar

or to participate

Keep Reading