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.
⏱️ The 45-Minute Framework
📋 Requirements Gathering Questions
Always ask before designing:| Category | Questions to Ask | Why It Matters |
|---|---|---|
| Scope | What are the must-have features? What can we skip? | Prevents over-engineering |
| Users | Who interacts with the system? (Customer, Admin, System) | Defines access control needs |
| Scale | Single machine or distributed? How many concurrent users? | Threading/locking decisions |
| Data | What needs to be persisted? Any real-time requirements? | Database design |
| Edge Cases | What happens on failure? Concurrent access handling? | Shows senior thinking |
🏗️ OOP Quick Reference
The Four Pillars - One-Liner Each
| Pillar | What It Means | Code Signal |
|---|---|---|
| Encapsulation | Hide internals, expose interface | private fields + public methods |
| Abstraction | Define WHAT, hide HOW | abstract classes, interfaces |
| Inheritance | Share behavior from parent | extends / : Base |
| Polymorphism | Same interface, different behavior | Override methods, common interface |
⚡ SOLID Principles
| Principle | One-Liner | Code Smell |
|---|---|---|
| S - Single Responsibility | One class = one reason to change | Class has 10+ methods doing different things |
| O - Open/Closed | Open for extension, closed for modification | Adding feature requires editing existing classes |
| L - Liskov Substitution | Subtypes must be substitutable | Subclass throws “not implemented” exceptions |
| I - Interface Segregation | Many specific interfaces > one general | Class implements interface methods it doesn’t need |
| D - Dependency Inversion | Depend on abstractions, not concretions | Class instantiates dependencies with new |
Quick Examples
🧩 Design Patterns Cheat Sheet
When to Use What?
| Situation | Pattern | Example |
|---|---|---|
| Need only ONE instance | Singleton | Database, Logger, Config |
| Create objects without specifying class | Factory | Vehicles, Notifications, Payments |
| Complex object with many optional parts | Builder | Pizza, HTTP Request, Query |
| Make incompatible interfaces work | Adapter | Legacy system integration |
| Add features dynamically | Decorator | Coffee toppings, Middleware |
| Simplify complex subsystem | Facade | Video converter, Payment gateway |
| Swap algorithms at runtime | Strategy | Sorting, Payment, Shipping |
| Notify many objects of changes | Observer | Stock price, Notifications |
| Object behavior changes with state | State | Order status, Elevator, ATM |
| Undo/redo operations | Command | Text editor, Transactions |
Pattern Code Templates
- Singleton
- Factory
- Strategy
- Observer
- State
📊 UML Class Diagram Notation
🚨 Common Interview Problems
Parking Lot System
Elevator System
Library Management
Chess Game
Interview Questions
The 45-Minute Framework allocates 5 minutes to requirements. You are designing a Parking Lot system and the interviewer says 'just design it.' What are the five questions you ask before drawing anything, and why does each one change your design?
The 45-Minute Framework allocates 5 minutes to requirements. You are designing a Parking Lot system and the interviewer says 'just design it.' What are the five questions you ask before drawing anything, and why does each one change your design?
- “How many floors and total spots?” — This determines whether ParkingLot has a
List[Floor](multi-floor with composition) or is a flat structure. A 5000-spot garage needs floor-level indexing for O(1) spot lookup; a 20-spot surface lot does not. - “What vehicle types — just cars, or also motorcycles, buses, handicapped?” — This drives the ParkingSpot hierarchy. If buses take 5 contiguous spots, you need a SpotAllocation strategy, not just a 1:1 Vehicle-to-Spot mapping. If handicapped spots exist, you need priority rules and potentially a separate SpotType enum.
- “Do we need entry/exit gates, or just manual tracking?” — Gates introduce a Gate class with sensor integration, a Ticket class with entry timestamps, and a payment flow on exit. Without gates, the design is simpler but loses the billing model entirely.
- “Multiple payment methods or just flat rate?” — This decides whether you need a PaymentStrategy interface (cash, card, mobile) or a simple
calculate_fee(duration)method. It also determines whether you need a Rate class for hourly/daily/monthly pricing tiers. - “Concurrent access — can two cars try the same spot simultaneously?” — This is the question that separates junior from senior designs. If yes, you need a locking mechanism on spot assignment (optimistic locking, mutex, or database-level constraint). If no, the design is simpler but unrealistic.
- Each answer eliminates or adds 1-3 classes from the design. Without asking, you either over-engineer (designing for buses when the lot only handles cars) or under-engineer (ignoring concurrency in a 5000-spot garage).
- The interviewer answers: “It’s a multi-floor garage with cars and motorcycles only, flat hourly rate, no gates.” How does this specific set of constraints change your class diagram compared to the “full” parking lot design?
- You asked about concurrency but the interviewer says “don’t worry about it.” Should you still design for thread safety, or is that over-engineering?
The cheat sheet says 'identify nouns for classes, identify verbs for methods.' But this heuristic can lead you astray. Give me an example where blindly following it produces a bad design, and what you would do instead.
The cheat sheet says 'identify nouns for classes, identify verbs for methods.' But this heuristic can lead you astray. Give me an example where blindly following it produces a bad design, and what you would do instead.
- Classic trap: “The customer places an order, the system validates the payment, sends a confirmation email, and updates inventory.” Nouns: Customer, Order, System, Payment, Email, Inventory. If you make “System” a class, you have created a God class. If you make “Email” a class, you have confused a message with a service. If you make “Payment” a class, you need to decide whether it is the payment data (amount, method, status) or the payment action (processing, refunding).
- The heuristic works for core domain objects (Customer, Order, Product) but fails for actions, services, and cross-cutting concerns. “Validates” should not become a ValidatesClass — it should be a method on a ValidationService. “Sends” should not become a class — it is a method on NotificationService.
- The better approach: identify nouns for entities (things with identity and lifecycle), then identify responsibilities (not just verbs) for services. Ask: “What data does this own?” for entities, and “What behavior does this orchestrate?” for services. This gives you Order (entity: holds items, total, status), PaymentService (service: processes charges, handles refunds), and NotificationService (service: sends emails, SMS).
- I also look for the “information expert” pattern: assign behavior to the class that has the data needed to perform it.
order.calculateTotal()belongs on Order because Order has the items and prices.paymentService.charge(order)belongs on PaymentService because it has the gateway credentials.
- In Domain-Driven Design, there is a distinction between Entities, Value Objects, and Services. How does this map to the noun/verb heuristic, and where does each fit in a class diagram?
- The requirement says “the system generates a PDF invoice.” Is Invoice a class, a method, or both? Walk through your reasoning.
The Strategy pattern cheat sheet shows swapping PaymentStrategy at runtime via `cart.strategy = PayPal()`. In a real production system, who decides which strategy to use, and where does that decision live?
The Strategy pattern cheat sheet shows swapping PaymentStrategy at runtime via `cart.strategy = PayPal()`. In a real production system, who decides which strategy to use, and where does that decision live?
- In a real system, the strategy selection is driven by user input (the customer picks their payment method at checkout), configuration (the system supports certain methods per region), or business rules (orders over $500 require wire transfer). The decision lives in a Factory or a configuration service, not in the client code.
- The pattern in production: the UI sends a
payment_method: "paypal"field. APaymentStrategyFactorymaps that string to a concrete strategy:strategies = {"paypal": PayPalStrategy, "credit_card": CreditCardStrategy, "apple_pay": ApplePayStrategy}. The factory returns the strategy, and the CheckoutService uses it without knowing which one it got. - The factory itself might be configured per deployment. In the US, you support Stripe and PayPal. In India, you support Razorpay and UPI. The factory reads a config file or feature flag to determine which strategies are available. This means adding a new payment method requires: (1) writing a new strategy class, (2) adding it to the factory mapping, (3) enabling it in the config. No existing code is modified — that is OCP in action.
- The anti-pattern to avoid: putting the strategy selection in an if/elif chain inside CheckoutService. That defeats the purpose of the pattern because adding a new method requires modifying CheckoutService.
- What happens if the selected payment strategy fails (Stripe is down)? Do you fall back to another strategy, return an error, or retry? How does this affect the Strategy pattern’s design?
- How would you handle a payment method that requires additional data (credit card needs card number, bank transfer needs routing number) while keeping the Strategy interface uniform?
The Singleton pattern is listed for Database, Logger, and Config. Many experienced engineers consider Singleton an anti-pattern. Make the case for AND against Singleton, and tell me what you would use instead.
The Singleton pattern is listed for Database, Logger, and Config. Many experienced engineers consider Singleton an anti-pattern. Make the case for AND against Singleton, and tell me what you would use instead.
- The case for Singleton: some resources genuinely should have only one instance. A database connection pool should be shared to avoid exhausting connections. A configuration loader should read the file once. A logger should funnel all output through one handler to avoid interleaved writes. Singleton enforces this at the language level.
- The case against: Singleton is global mutable state in disguise. It makes testing extremely difficult because you cannot inject a mock — the class hardcodes its own instance. It hides dependencies (a class uses
Database.instance()deep inside a method, invisible from the constructor signature). It creates tight coupling across the codebase. And in multi-threaded environments, the double-checked locking pattern is easy to get wrong. - What I use instead: dependency injection with a single instance. Create ONE instance of Database in the composition root (main.py) and inject it into every service that needs it. The effect is the same (only one instance exists) but the benefits are enormous: dependencies are explicit, testing is trivial (inject a mock), and the lifecycle is controlled by the application, not by the class itself.
- The only case where I still use Singleton: when the instance must be globally accessible from code I do not control (framework hooks, third-party library callbacks). Even then, I prefer a module-level instance in Python (which is effectively a singleton without the pattern overhead).
- In Python, a module is imported once and cached. Is a module-level variable effectively a Singleton? What are the differences?
- How would you test a class that uses
Database.instance()internally? Walk me through the refactoring steps to make it testable.
The cheat sheet mentions 'What happens if two users try to book the same spot?' as a discussion point. Actually answer it: how do you handle concurrent access to a shared resource in an LLD design?
The cheat sheet mentions 'What happens if two users try to book the same spot?' as a discussion point. Actually answer it: how do you handle concurrent access to a shared resource in an LLD design?
The Observer pattern is shown for stock price notifications. What happens when you have 10,000 observers and the stock price changes 100 times per second? Where does this pattern break down?
The Observer pattern is shown for stock price notifications. What happens when you have 10,000 observers and the stock price changes 100 times per second? Where does this pattern break down?
- At 10,000 observers and 100 updates per second, you are making 1 million
update()calls per second. If each call takes 1ms (network I/O, database write, email send), the notification loop takes 10 seconds — by which time 1,000 more price changes have queued up. The system falls behind and eventually runs out of memory or crashes. - The core problem: the naive Observer pattern is synchronous and unbounded. The
notify()method iterates through all observers sequentially in the same thread, and there is no backpressure mechanism. - Fix 1: Async notification. Instead of calling
update()directly, publish a message to a queue (Redis, Kafka, RabbitMQ). Each observer consumes from the queue at its own pace. This decouples the publisher from the consumer’s speed. - Fix 2: Batching and throttling. Instead of notifying on every price change, batch updates and notify once per second with the latest price. Most observers do not need 100 updates per second — a 1-second resolution is sufficient for a dashboard.
- Fix 3: Filtering. Not all observers care about every stock. Add a subscription filter:
attach(observer, stock_symbol). The subject only notifies observers registered for the specific stock that changed, reducing the fan-out from 10,000 to perhaps 50 per update. - In production at places like Bloomberg or Robinhood, the pattern evolves into a pub/sub system with topic-based routing, consumer groups, and exactly-once delivery guarantees. The Observer pattern is the seed; the production system is the tree.
- If an observer’s
update()method throws an exception, should it stop the notification loop for all other observers? How do you handle this in your design? - How would you modify the Observer pattern to support “unsubscribe during notification” — an observer that removes itself inside its own
update()callback? What concurrency issue does this create?
The State pattern example shows Document transitioning Draft -> Review -> Published. The interviewer says: 'Add a Rejected state that can go back to Draft.' How does this change your design, and what new problem does it introduce?
The State pattern example shows Document transitioning Draft -> Review -> Published. The interviewer says: 'Add a Rejected state that can go back to Draft.' How does this change your design, and what new problem does it introduce?
- Adding Rejected is straightforward: create a
RejectedStateclass that implementshandle()by transitioning back to DraftState. ReviewState’shandle()now needs to decide between Published and Rejected. This introduces a branching decision that the original linear chain did not have. - The new problem: who decides whether the review passes or fails? The current
handle(context)method takes no input. You need to add a parameter:handle(context, action)where action is “approve” or “reject.” Or better, split into explicit methods:approve(context)transitions to Published,reject(context)transitions to Rejected. This makes the valid transitions self-documenting. - The deeper design issue is state transition validation. With the linear chain, any call to
handle()moves forward. With branching, you need to prevent invalid transitions: you cannot “approve” from Draft (must go through Review first), and you cannot “reject” from Published (already published). Each state must validate the requested action and raise an error for invalid transitions. - In production, this is where a State Machine library (like
transitionsin Python or Spring State Machine in Java) earns its keep. It provides a declarative transition table:transitions = [(Draft, 'submit', Review), (Review, 'approve', Published), (Review, 'reject', Rejected), (Rejected, 'revise', Draft)]. This is easier to audit, test, and extend than scattered if-statements in state classes.
- The product manager says “Published documents should be retractable — they can go back to Draft for corrections.” Now you have a cycle in your state machine. What new risks does a cyclic state machine introduce compared to an acyclic one?
- How would you log or audit every state transition for compliance purposes? Where does that cross-cutting concern live in the State pattern — in the states, in the context, or in a separate observer?
You have memorized all the design patterns on this cheat sheet. The interviewer gives you a problem and asks: 'Which patterns would you use?' You name three patterns. The interviewer responds: 'That is over-engineered. I just need a working solution.' How do you recover?
You have memorized all the design patterns on this cheat sheet. The interviewer gives you a problem and asks: 'Which patterns would you use?' You name three patterns. The interviewer responds: 'That is over-engineered. I just need a working solution.' How do you recover?
- This is a common trap. The interviewer is testing whether I can prioritize simplicity over pattern-matching. I would say: “You are right — let me step back. Let me start with the simplest design that meets the core requirements, and we can add patterns only where the design demands them.”
- I would strip back to plain classes with direct dependencies. No Strategy pattern unless there are actually multiple interchangeable algorithms. No Observer unless there are actually multiple listeners that need notification. No Factory unless object creation logic is actually complex enough to warrant it.
- The mistake candidates make: treating patterns as mandatory ingredients rather than tools that solve specific problems. The Singleton pattern exists to solve “I need exactly one instance.” If you do not have that problem, do not use Singleton. The Strategy pattern exists to solve “I need to swap algorithms at runtime.” If there is only one algorithm today, a plain method is simpler.
- The recovery phrase: “I was anticipating future extensibility, but you are right that the current requirements do not need that complexity. Let me simplify.” Then sketch the simplest solution that works. If the interviewer later asks “how would you add a new payment method?” — THAT is when you say “I would extract a PaymentStrategy interface at that point.”
- This demonstrates the most senior-level skill of all: knowing when NOT to apply what you know. Restraint is harder than knowledge.
- How do you decide, during a live interview, whether a pattern earns its complexity? What is your personal heuristic for “simple enough to not need a pattern”?
- Name a design pattern that you think is almost always worth its complexity, and one that you think is almost never worth it in practice.
❓ Interview Discussion Points
Be ready to answer:- “Why did you use inheritance here instead of composition?”
- “How would you add a new payment method?”
- “What happens if two users try to book the same spot?”
- “How would you handle this at 10x scale?”
- “Where would you add logging/monitoring?”
- “How would you test this design?”
- “I chose X over Y because…” — Always justify. Interviewers want reasoning, not just decisions.
- “The trade-off here is…” — Acknowledging trade-offs shows you understand that every design choice has a cost.
- “This follows the Open/Closed principle because…” — Naming the principle demonstrates conscious design thinking.
- “Adding new feature Z would just require creating a new class implementing Y” — This proves your design is extensible.
- “For concurrency, we could use a lock here, though a read-write lock would give us better throughput for read-heavy workloads” — Showing you can reason about performance trade-offs at the design level.
- “I am separating this into its own service because it has a different rate of change than the core logic” — This is SRP at its most articulate.
- “Let me start with the happy path and then we can discuss edge cases” — Shows you prioritize and manage scope.
✅ Pre-Interview Checklist
Before the Interview
Before the Interview
- Review the 4 pillars of OOP
- Memorize SOLID principles
- Know 5-6 key design patterns cold
- Practice 2-3 problems end-to-end
- Have your IDE or whiteboard ready
- Review this cheat sheet!
During the Interview
During the Interview
- Clarify requirements FIRST (spend 5 min)
- Think out loud - explain your reasoning
- Start simple, then add complexity
- Draw class diagram before coding
- Apply patterns where appropriate
- Mention edge cases even if you don’t code them
Common Mistakes to Avoid
Common Mistakes to Avoid
- Don’t jump into coding immediately
- Don’t over-engineer with unnecessary patterns
- Don’t forget about thread safety for shared resources
- Don’t ignore error handling completely
- Don’t make assumptions - ask questions
- Don’t try to implement everything perfectly
Interview Deep-Dive
An interviewer gives you a problem you have never seen before. You have 45 minutes. Walk me through your first five minutes -- what exactly do you say and do?
An interviewer gives you a problem you have never seen before. You have 45 minutes. Walk me through your first five minutes -- what exactly do you say and do?
- I open by restating the problem in my own words to confirm understanding. Then I spend the full five minutes asking clarifying questions, organized by category.
- Scope: “What are the three or four core features you want me to focus on? What is explicitly out of scope?” This prevents me from over-engineering. If the interviewer says “skip notifications,” I do not waste time designing a NotificationService.
- Actors: “Who interacts with the system? End users, admins, internal services?” This drives my access control and class hierarchy decisions.
- Scale: “Is this a single instance or distributed? How many concurrent users should I assume?” This determines whether I need to think about thread safety, locking, or eventual consistency.
- Constraints: “Are there any specific technology requirements, or should I design language-agnostically?”
- I write the answers on the whiteboard as a bulleted list so both the interviewer and I can reference them throughout the session. This demonstrates structured thinking before a single class is drawn.
- The goal is to leave the requirements phase with a shared, explicit scope. When I start drawing classes, every decision traces back to something the interviewer confirmed.
You are running out of time at the 35-minute mark and have not finished coding the core logic. How do you handle the last 10 minutes?
You are running out of time at the 35-minute mark and have not finished coding the core logic. How do you handle the last 10 minutes?
- I stop coding and switch to explaining. The worst thing I can do is keep typing and run out the clock silently. Instead, I say: “Let me describe the remaining implementation and the design decisions behind it.”
- I outline the methods I would implement, the edge cases I would handle, and the patterns I would apply. For example: “The checkout method would use the Strategy pattern for payment, validate the seat lock has not expired, and wrap the database write in a transaction with retry logic.”
- I also proactively discuss extensibility: “If we needed to add group booking, I would create a GroupReservation class that holds multiple individual reservations and applies a bulk discount strategy.”
- Interviewers evaluate thought process as much as code. A candidate who explains three remaining methods clearly and discusses trade-offs scores higher than one who writes two more methods in silence but cannot explain why they made any choice.
- The key phrases: “Here is what I would do next,” “The trade-off I would consider is,” and “This design handles the extension of X by adding a new class implementing Y.”
The interviewer asks: 'Your design has six classes. How would you test it?' Walk through your testing strategy for an LLD design.
The interviewer asks: 'Your design has six classes. How would you test it?' Walk through your testing strategy for an LLD design.
- I approach testing in three layers, matching the structure of the design.
- Unit tests for individual classes: each class with business logic gets its own test file. I test the PricingService with mock Orders, the PaymentProcessor with a MockPaymentGateway (thanks to DIP, injecting mocks is trivial), and the InventoryService with in-memory data.
- Integration tests for class interactions: I test that the CheckoutService correctly coordinates PricingService, PaymentProcessor, and InventoryService. These tests use real implementations but with test data. The key scenarios: happy path (order succeeds), payment failure (order rolls back), insufficient inventory (order rejected), and concurrent booking (two users try the same resource).
- Edge case tests: null inputs, empty collections, boundary values (exactly at capacity, exactly at zero balance), and state transitions that should be invalid (cancelling an already-delivered order).
- Because the design follows SOLID, each class has a narrow interface and takes its dependencies through the constructor. This means I can test each class in isolation without standing up the entire system. A God class with hardcoded dependencies would require mocking five things just to test one method — that is the testability payoff of SRP and DIP.
- I would mention but not implement: concurrency tests using threading and assertions about atomicity, and performance tests for hot paths like seat lookup or spot assignment.