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
SDE 1, Smartbiz @Amazon: Apply Here
Software Development Engineer (2026) @Amazon: Apply Here
Software Engineer (L3/L4, University Graduate 2026) @Google: Apply Here
SDE1, Full Stack @Amazon: Apply Here

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, LoggerWhere 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._instanceWhere 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 stepsWhere 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! 🚀
See you in the next one!
Signing Off, Scortier

