Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
📊 Pattern Overview
Design patterns are proven solutions to common software design problems. They are not inventions — they are discoveries. The Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) catalogued these patterns in 1994 by studying what experienced developers did repeatedly. Think of patterns as a shared vocabulary: when you say “let’s use a Strategy here,” every engineer on the team immediately understands the structure, the trade-offs, and the intent. There are 23 classic patterns divided into 3 categories:Creational (5)
Structural (7)
Behavioral (11)
🎯 Pattern Decision Tree
Use this to quickly identify which pattern fits your problem:🔵 Creational Patterns
How objects are createdSingleton
Factory
Builder
1. Singleton Pattern
- Thread-Safe
- Python Module
- Metaclass
2. Factory Pattern
Creates objects without specifying exact classes. Factory encapsulates the “which class to instantiate” decision, so the calling code depends only on the interface, not the concrete type. This directly supports the Open/Closed Principle: when you add a new notification channel, you update the factory’s registry — the rest of your codebase never changes.3. Builder Pattern
Constructs complex objects step by step. Builder shines when an object has many optional parameters and you want a readable, fluent API instead of a constructor with 15 arguments. The key insight is separating construction from representation — the same building process can produce different results. In real-world codebases, you will see Builder used for query builders (SQLAlchemy), HTTP request builders, and configuration objects.Structural Patterns
How objects are composed4. Adapter Pattern
Makes incompatible interfaces work together.5. Decorator Pattern
Adds behavior to objects dynamically.6. Facade Pattern
Provides simple interface to complex subsystem.Behavioral Patterns
How objects communicate7. Strategy Pattern
Defines a family of interchangeable algorithms. Strategy is arguably the most important pattern for LLD interviews because it directly implements the Open/Closed Principle. The core idea: extract varying behavior into its own class hierarchy, then inject the specific variant at runtime. Whenever you see a decision that might change (pricing rules, sorting algorithms, payment processing), Strategy is your go-to pattern. It turns compile-time decisions into runtime decisions, giving you flexibility without modifying existing code.8. Observer Pattern
Notifies multiple objects about state changes. Observer implements a one-to-many dependency so that when one object changes state, all dependents are notified automatically. This is the foundation of event-driven architectures, UI frameworks (React’s state management), and pub/sub systems. The critical design insight is decoupling: the subject (publisher) does not know or care about the specific observers (subscribers). This means you can add new notification channels, analytics hooks, or audit logging without touching the core business logic.9. State Pattern
Object behavior changes based on internal state. State pattern eliminates sprawling if/elif chains that check the current state before deciding what to do. Instead, each state becomes its own class with its own behavior. The context object delegates to the current state, and state transitions happen by swapping the state object. This is the go-to pattern for modeling lifecycles: order processing (Pending, Confirmed, Shipped, Delivered), elevator systems (Moving, Stopped, Maintenance), and vending machines (Idle, HasMoney, Dispensing). When interviewers hear you say “I’ll model this as a state machine,” they know you are thinking like a senior engineer.Pattern Selection Guide
| Problem | Pattern |
|---|---|
| Need single instance | Singleton |
| Create objects without knowing class | Factory |
| Complex object construction | Builder |
| Integrate incompatible interfaces | Adapter |
| Add features dynamically | Decorator |
| Simplify complex subsystem | Facade |
| Interchangeable algorithms | Strategy |
| Notify of state changes | Observer |
| Behavior changes with state | State |
🔥 Top Patterns for Interviews
These patterns appear most frequently in LLD interviews:Must-Know (80% of interviews)
| Pattern | Common Use Cases |
|---|---|
| Factory | Vehicle types, Payment methods, Notification channels |
| Strategy | Payment processing, Shipping calculation, Pricing rules |
| Observer | Stock price alerts, Order status, Pub/Sub systems |
| State | Order lifecycle, Elevator, Vending machine, ATM |
| Singleton | Database, Configuration, Logger |
Good to Know (20% of interviews)
| Pattern | Common Use Cases |
|---|---|
| Builder | Complex configurations, Query builders |
| Decorator | Middleware, Permission layers |
| Adapter | Legacy integration, Third-party APIs |
| Command | Undo/Redo, Transaction logs |
| Facade | Complex subsystem APIs |
📚 Pattern Diagrams
📝 Pattern Comparison Table
| Pattern | Intent | Use When | Avoid When |
|---|---|---|---|
| Singleton | One instance globally | DB connection, Config | Need testability, multiple instances |
| Factory | Create objects without specifying class | Multiple product types | Only one concrete class |
| Builder | Complex object construction | Many optional parameters | Simple objects |
| Strategy | Swap algorithms at runtime | Multiple ways to do something | Only one algorithm |
| Observer | Notify subscribers of changes | Decoupled event handling | Few, known subscribers |
| State | Object behavior changes with state | Finite state machine | Simple conditionals suffice |
| Decorator | Add behavior dynamically | Layered functionality | Inheritance works fine |
| Facade | Simplify complex subsystems | Complex library integration | Simple interfaces already |
| Command | Encapsulate requests as objects | Undo/redo, queuing | Simple direct calls |
💡 Interview Tips
When to Mention Patterns
When to Mention Patterns
- Do: “I’ll use Factory here because we need to create vehicles without knowing the exact type at compile time”
- Don’t: “Let me apply all 23 GoF patterns to show I know them”
Pattern Trade-offs
Pattern Trade-offs
| Pattern | Benefit | Cost |
|---|---|---|
| Singleton | Global access, one instance | Hard to test, hidden dependencies |
| Factory | Decoupled creation | More abstraction layers |
| Observer | Loose coupling | Memory leaks if not detached |
| Strategy | Swappable algorithms | More classes to manage |
| Decorator | Flexible composition | Complex debugging |
Combining Patterns
Combining Patterns
- Factory + Strategy: Create strategies via factory
- Observer + Singleton: Global event bus
- State + Factory: Create states via factory
- Decorator + Factory: Create decorators dynamically
- Command + Memento: Undo with snapshots
Common Interview Questions
Common Interview Questions
- “Why did you use X pattern here?”
- “What’s the difference between Strategy and State?”
- “How would you add a new payment method?” (Strategy)
- “How would you implement undo/redo?” (Command + Memento)
- “How would you prevent double instantiation?” (Singleton with locking)
Interview Deep-Dive Questions
Q1: You're designing a notification system that supports email, SMS, push, and Slack. A junior engineer suggests using a big if/elif chain to pick the channel. Walk me through why you would (or wouldn't) use a pattern here, and which one.
Q1: You're designing a notification system that supports email, SMS, push, and Slack. A junior engineer suggests using a big if/elif chain to pick the channel. Walk me through why you would (or wouldn't) use a pattern here, and which one.
- The if/elif chain works fine for 4 channels today, but the real question is: how often will this list change? If the answer is “every quarter when marketing adds a new channel,” then every change forces you to modify the dispatching function, re-test it, and risk breaking existing channels. That violates the Open/Closed Principle — the code is not closed for modification.
- I would use the Strategy pattern here. Define a
NotificationChannelinterface with asend(message, recipient)method. Each channel (Email, SMS, Push, Slack) is a concrete strategy. The calling code receives the strategy via dependency injection or a factory, and callsstrategy.send()without knowing or caring which channel it is. - The factory that maps
"email"toEmailChannelis the only place that needs updating when a new channel is added. The dispatching code, the business logic, and all existing channel implementations remain untouched. - However, I would not use this pattern if the system will only ever have 2-3 channels and the selection logic has domain-specific rules (e.g., “send SMS only if user is in the US and has opted in, otherwise fall back to email”). In that case, the if/elif chain captures the business logic more clearly than a pattern. Patterns should reduce complexity, not add indirection for its own sake.
- The key insight for the interviewer: I am not choosing the pattern because “GoF says so.” I am choosing it because the axis of change (new channels being added frequently) aligns with what Strategy is designed to handle — isolating the varying behavior behind an interface.
- What if each notification channel requires completely different configuration (email needs SMTP settings, SMS needs a Twilio API key, push needs device tokens)? How do you handle the construction of these strategy objects — and does that suggest a second pattern?
- Now the product manager says “some notifications should go to multiple channels simultaneously.” How does your design change? Does Strategy still work, or do you need Observer/Chain of Responsibility?
Q2: What's the difference between Strategy and State pattern? They look structurally identical -- same UML diagram, same delegation pattern. How do you decide which to use?
Q2: What's the difference between Strategy and State pattern? They look structurally identical -- same UML diagram, same delegation pattern. How do you decide which to use?
- You are right that the UML is nearly identical: both have a Context that delegates to an interface, with multiple concrete implementations. The difference is intent and who controls transitions.
- Strategy: The client chooses which algorithm to inject. The context does not change its strategy on its own. Example: a payment system where the user picks credit card vs PayPal at checkout. The
ShoppingCartdoes not spontaneously switch from CreditCard to PayPal — the user (external actor) makes that choice. - State: The object itself transitions between states based on internal logic. The context’s behavior changes over time as its state changes. Example: an order that moves from Pending to Processing to Shipped to Delivered. The order transitions itself — no external actor says “now be in Shipped state.” Each state class knows which state comes next and triggers the transition.
- The practical test: if you see
context.set_strategy(new_strategy)called by client code, it is Strategy. If you seeself.context.state = NextState()called from within a state class, it is State. - Another heuristic: Strategy objects tend to be stateless (they are pure algorithms), while State objects often carry state-specific data and have transition logic. In a State pattern, the state classes form a finite state machine with defined transitions; in Strategy, the strategies are independent and do not know about each other.
- A real-world example that blurs the line: a rate limiter that switches between
AllowAll,Throttle, andBlockAllstrategies based on current load. Is this Strategy (swapping algorithms) or State (the system transitions between states)? If the system itself monitors load and transitions automatically, it is State. If an ops engineer manually flips a switch, it is Strategy. Same structure, different intent.
- Can you give me an example of a system where you might start with Strategy and later realize it should be State (or vice versa)? What would trigger that refactoring?
- In the State pattern implementation shown earlier, each state class creates the next state object directly (e.g.,
order.state = ProcessingState()). What is the problem with this coupling, and how would you fix it?
Q3: Name three situations where using a design pattern is the WRONG choice -- where the pattern makes the codebase worse, not better.
Q3: Name three situations where using a design pattern is the WRONG choice -- where the pattern makes the codebase worse, not better.
- Singleton for everything “shared”: Teams often reach for Singleton for any class they want globally accessible — configuration, logging, feature flags, API clients. The problem: Singleton hides dependencies. A function that internally accesses
DatabaseConnection.get_instance()has an invisible dependency on the database — its signature does not reveal this. Unit testing becomes painful because you cannot substitute a mock without monkey-patching global state. In one project I worked on, 14 Singletons created a hidden dependency graph that made the startup order brittle — service A’s singleton initialized before service B’s was ready, causing intermittent failures. The fix: dependency injection. Pass the database connection as a constructor argument. It is more explicit, more testable, and the “single instance” guarantee can be enforced at the composition root (the main function or DI container) rather than inside the class. - Observer pattern with too many subscribers and no backpressure: Observer is elegant for loose coupling, but in a high-throughput system, a subject notifying 200 observers synchronously on every state change turns a single write into 200 function calls on the same thread. If any observer is slow (e.g., one that writes to disk), it blocks the entire notification chain. I have seen this in event-driven systems where adding a new “just log this event” observer increased p99 latency by 300ms because the logger was doing synchronous I/O. The fix: either use async notification (message queue, event bus), or switch to a pub/sub system that decouples the producer’s latency from the consumers. The pattern itself does not tell you to think about backpressure — that is your job.
- Factory pattern for a single concrete class: If
NotificationFactory.create("email")always returnsEmailNotificationand there are no other notification types (and no realistic plan to add them), the factory is pure ceremony. It adds a layer of indirection, an extra file, and a string-based lookup that could fail at runtime — all for no benefit. This is the most common pattern anti-pattern: applying a pattern “just in case” against the YAGNI principle. Build the factory when the second or third concrete type actually materializes, not speculatively. - Bonus: Decorator stacks that are impossible to debug. When you have
LoggingDecorator(RetryDecorator(TimeoutDecorator(AuthDecorator(HttpClient())))), a stack trace for an error is 8 layers deep, and understanding which decorator caught or transformed the exception requires reading every wrapper. At some point, a middleware pipeline (like Django/Express middleware) is clearer because it has explicit ordering and standardized hooks.
- You are reviewing a pull request from a junior engineer who added a Factory, Strategy, and Observer to a feature that currently has one payment method and one notification channel. How do you give constructive feedback?
- How do you decide at what point to introduce a pattern? What heuristic or “rule of thumb” do you use to avoid both premature abstraction and too-late refactoring?
Q4: How does the Decorator pattern differ from subclassing? When is Decorator strictly better, and when is inheritance the right call?
Q4: How does the Decorator pattern differ from subclassing? When is Decorator strictly better, and when is inheritance the right call?
- With inheritance, you create a new class for each combination of features. If you have a
Coffeebase class and want Milk, Sugar, and Whip as options, inheritance requires:MilkCoffee,SugarCoffee,WhipCoffee,MilkSugarCoffee,MilkWhipCoffee,SugarWhipCoffee,MilkSugarWhipCoffee— that is 2^N classes for N options. This is the “class explosion” problem. With Decorator, you have 3 decorator classes and compose them at runtime:Whip(Sugar(Milk(Coffee()))). Adding a 4th option (Caramel) adds 1 decorator class instead of doubling the class count. - Decorator is strictly better when: (a) you need to combine behaviors in arbitrary ways at runtime, (b) the set of behaviors grows independently, (c) you want to add/remove capabilities dynamically (e.g., toggling features via configuration).
- Inheritance is the right call when: (a) the relationship is genuinely “is-a” and behavior is not composable (a
Dogis anAnimal, not a decoratedAnimal), (b) the subclass needs access to protected internals of the parent (decorators only access the public interface), (c) the hierarchy is shallow and stable — adding a subclass is cheaper than the decorator infrastructure. - A real-world example where Decorator shines: middleware in web frameworks. Each middleware (authentication, logging, rate limiting, compression) wraps the handler. You compose them in a chain, and you can reorder or disable them via configuration. Doing this with inheritance would be absurd.
- A real-world example where inheritance is better: UI widget hierarchies (Button extends Widget). A Button is not a “decorated Widget” — it has fundamentally different rendering logic, not just added behavior on top of Widget’s rendering.
- The gotcha: Decorator requires that all decorators and the base component share the same interface. If the base interface is large (20 methods), every decorator must delegate all 20 methods, which is tedious and error-prone. In Python,
__getattr__delegation can help, but in strongly typed languages like Java, it is boilerplate-heavy. This is where abstract decorator base classes (likeCoffeeDecoratorin the example) help — they provide default delegation so concrete decorators only override what they change.
- In Python,
functools.wrapsand function decorators (@decorator) look similar to the Decorator pattern but are actually different. What is the relationship, and can you use Python’s@decoratorsyntax to implement the GoF Decorator pattern? - You have a
Streamclass withread()andwrite()methods. You want to add encryption, compression, and buffering as optional layers. Walk me through how you design this with the Decorator pattern and what the call flow looks like when a client callsread().
Q5: You see this code in a pull request. What pattern (or anti-pattern) is it, and what would you change?
Q5: You see this code in a pull request. What pattern (or anti-pattern) is it, and what would you change?
- This is a textbook candidate for the State pattern. The code uses string-based status checking and a growing if/elif chain to determine behavior. Every new state (e.g., “refunded”, “cancelled”, “returned”) adds another branch, and the logic for each state is tangled together in one method.
- Problems with this code: (a) Open/Closed violation — adding a new state requires modifying
process(), risking bugs in existing states. (b) No compile-time safety —order.statusis a string, so a typo like"valdiated"would silently fail. (c) State-specific logic is scattered — what if “validated” orders need additional behavior (e.g., sending a confirmation email)? You would add it to the elif branch, making the method longer and harder to test. (d) Transition rules are implicit — nothing preventsorder.statusfrom being set to “delivered” directly from “pending,” skipping validation and payment. - Refactoring to State pattern: Each state becomes a class (
PendingState,ValidatedState,PaidState,ShippedState,DeliveredState). Each state class has aprocess(order)method that performs the state-specific action and transitions to the next state. TheOrderclass holds a reference to its current state and delegatesprocess()to it. Invalid transitions can raise exceptions in the state class (e.g.,DeliveredState.process()raises “already delivered”). - However, I would push back on when to refactor: if this is a startup with 4 states that rarely change, this if/elif is readable and works. I would add a TODO and refactor when the 5th or 6th state appears. The pattern is the right long-term architecture, but premature extraction costs development time and adds indirection that a new team member must learn.
- The string-based status is the more urgent fix regardless of the pattern question. At minimum, replace strings with an Enum (
class OrderStatus(Enum): PENDING = "pending" ...) to get IDE autocomplete and catch typos.
- After refactoring to the State pattern, how would you add a “cancelled” state that can be reached from Pending, Validated, or Paid (but not from Shipped or Delivered)? How does the State pattern make this easier or harder than the if/elif approach?
- What if this order processor needs to persist state to a database? How do you serialize and deserialize State pattern objects? What are the challenges?
Q6: The Observer pattern in the example uses synchronous notification. What happens at scale, and how do production systems like Kafka, React, or Django signals actually implement the Observer concept?
Q6: The Observer pattern in the example uses synchronous notification. What happens at scale, and how do production systems like Kafka, React, or Django signals actually implement the Observer concept?
- In the textbook Observer,
notify()iterates through observers and callsupdate()on each one synchronously, on the calling thread. This has three production problems: (a) Latency coupling — the slowest observer determines the total notification time. If 1 of 50 observers takes 500ms (e.g., writing to a slow external API), every state change takes at least 500ms. (b) Failure propagation — if an observer throws an exception, it can abort the notification loop, leaving the remaining observers un-notified (unless you wrap each call in try/catch). (c) Thread starvation — in a single-threaded event loop (like Node.js or a game loop), synchronous notification blocks the entire loop. - Kafka implements Observer at the infrastructure level: producers publish events to topics, consumers subscribe to topics. The crucial difference is asynchronous, persistent, buffered delivery. Producers do not wait for consumers. If a consumer is slow, events accumulate in the topic partition. Consumers can replay events, process at their own pace, and even go offline temporarily. This is the “Observer pattern at scale” — decoupled by a durable message broker.
- React (state management via
useState/useReducer): When state changes, React does not immediately re-render all subscribed components. It batches state updates, reconciles the virtual DOM, and only re-renders components whose props/state actually changed. This is asynchronous, batched notification with diffing — a highly optimized Observer. React 18’s concurrent mode takes this further by allowing React to interrupt and prioritize renders. - Django signals:
post_save,pre_delete, etc. are synchronous Observer hooks. When you callmodel.save(), all connected signal handlers execute synchronously in the same database transaction. This is a common footgun: a signal handler that sends an HTTP request to a third-party API slows down every save. The Django community’s recommendation: use signals only for decoupled app-to-app communication within the same process; for anything involving I/O, dispatch to Celery (async task queue) from the signal handler. - The evolution: Textbook Observer (in-process, synchronous) to Event Bus (in-process, async) to Message Queue (cross-process, persistent) to Event Streaming (cross-service, replayable, ordered). Each step adds infrastructure complexity but gains in decoupling, resilience, and scalability.
- How would you handle the case where an observer needs the notification to be exactly-once (e.g., charging a credit card)? How does this change the Observer implementation?
- In the textbook Observer, if observer A detaches observer B during the notification loop (because A’s update logic removes B), what happens? How would you make the notification loop safe against concurrent modification?
Q7: Explain the Singleton anti-pattern problem in detail. Why do experienced engineers often call it an anti-pattern, and what do they use instead?
Q7: Explain the Singleton anti-pattern problem in detail. Why do experienced engineers often call it an anti-pattern, and what do they use instead?
- Singleton has earned its “anti-pattern” reputation not because the pattern itself is flawed, but because it is dramatically overused for the wrong reasons. People use it for global access when what they actually need is single instance.
- Testing nightmare: A class that calls
Database.get_instance()internally cannot be tested with a mock database. You must monkey-patch the global singleton, which is fragile, order-dependent, and can bleed state between tests. In a test suite of 2000 tests, shared singleton state causes intermittent failures that are almost impossible to reproduce. - Hidden dependencies: When a function’s signature is
process_order(order)but it internally accessesConfig.instance(),Logger.instance(), andPaymentGateway.instance(), the function’s true dependencies are invisible. A new developer cannot understand what this function needs just by reading its signature. This is the antithesis of clean architecture. - Initialization ordering: If Singleton A depends on Singleton B during initialization, and B depends on A, you have a circular dependency that manifests as a crash or undefined behavior at startup. With dependency injection, the DI container detects this cycle at configuration time and gives you a clear error.
- What to use instead: Dependency Injection (DI). Define the interface, create the instance once at the application’s composition root (the
main()function or a DI container like Python’sdependency-injector), and pass it to everything that needs it. The result: same single instance in production, but tests can inject mocks, and every dependency is explicit in constructor signatures. - When Singleton is actually fine: Truly stateless utility classes (like a
Mathsingleton — though that is better as a module), or when the language/framework provides singleton semantics naturally (Python modules are singletons, Spring beans are singletons by default). The pattern is fine; the abuse of it for everything “shared” is the problem. - A concrete example: A team had a
FeatureFlags.instance()singleton that read flags from a file at startup. Unit tests could not test behavior with different flag combinations because the singleton loaded once and never reset. Switching to constructor injection (Service(feature_flags=flags)) let tests pass any flag configuration they wanted, and the production code still used a singleFeatureFlagsinstance created at startup.
- In Python specifically, how does module-level instantiation (
config = Config()inconfig.py) differ from the Singleton pattern? What are the advantages and disadvantages? - You inherit a codebase with 12 Singletons. You cannot rewrite everything at once. What is your incremental strategy to reduce Singleton dependency while keeping the system working?
Q8: You're in an LLD interview designing a parking lot system. The interviewer says 'use appropriate design patterns.' Walk me through your pattern selection reasoning -- not the parking lot design, just how you decide which patterns to apply where.
Q8: You're in an LLD interview designing a parking lot system. The interviewer says 'use appropriate design patterns.' Walk me through your pattern selection reasoning -- not the parking lot design, just how you decide which patterns to apply where.
- I do not start by thinking about patterns. I start by identifying the axes of change — the parts of the system that are most likely to vary or grow over time. Patterns are solutions to specific change scenarios.
- Axis 1 — Vehicle types (car, motorcycle, truck, EV): These share an interface (
park(),getSize()) but have different behaviors. This is classic Factory territory. AVehicleFactorymaps the vehicle type string from the entry sensor to a concrete class. When the lot adds EV charging spots, I add anElectricVehicleclass and update the factory — nothing else changes. - Axis 2 — Pricing strategies (hourly, daily, weekend, membership): The pricing logic varies by customer type and time. This is Strategy. A
PricingStrategyinterface withcalculateFee(entry_time, exit_time). TheParkingTicketholds a reference to the pricing strategy. When marketing adds a “holiday pricing” tier, it is a new strategy class — no modification to existing pricing logic. - Axis 3 — Spot assignment logic: How do you pick which spot to assign? Closest to entrance? Closest to exit? Distribute evenly across floors? This varies by lot and could even change at runtime (normal mode vs event mode). Again Strategy, but for a different axis. The
ParkingLotdelegates spot assignment to aSpotAssignmentStrategy. - Axis 4 — Notifications (spot available, lot full, payment receipt): Multiple systems need to react to events (display boards, mobile app, admin dashboard). This is Observer — the
ParkingLotis the subject, displays and apps are observers. - Axis 5 — Payment processing: Multiple payment methods. Strategy again —
PaymentStrategyinterface withCreditCard,Cash,MobilePaymentimplementations. - What I would not use: Singleton for the
ParkingLotclass (a common mistake). There is nothing preventing a company from managing multiple lots. Even if there is “only one lot,” making it a Singleton adds global state. I would create one instance and pass it through constructor injection. - My reasoning framework: for each entity or behavior, I ask: (a) Will there be multiple variants? If yes, Factory or Strategy. (b) Does behavior change based on state? State pattern. (c) Do multiple components need to react to changes? Observer. (d) Is the construction complex? Builder. If the answer is “no” to all of these, plain classes and simple methods are fine — no pattern needed.
- The interviewer pushes back: “You used Strategy for both pricing and spot assignment. Is that not confusing? How do you keep the codebase clear when the same pattern appears multiple times?” How do you respond?
- Six months after launch, the parking lot needs to support “reserved spots” that are pre-booked by time slot. Which existing pattern in your design accommodates this, or do you need a new one?