This framework provides a systematic approach to tackle any LLD interview problem. Follow these steps sequentially to design robust, scalable, and maintainable object-oriented systems.
1. Understanding Requirements and Clarifying Assumptions
The CRUD-F Method
Before writing any code, clarify the following:
Category
Questions to Ask
Core
What is the primary purpose? Who are the users?
Requirements
What are the functional requirements? What's out of scope?
Users
How many users? Different user types?
Data
What data needs to be stored? What are the relationships?
Flows
What are the main user journeys? What are edge cases?
Example: BookMyShow System
Clarifying Questions:
- Do we need to handle multiple cities/theaters?
- Are we supporting different seat types (VIP, regular)?
- Do we need payment processing or just booking?
- How do we handle concurrent bookings?
- Should we support cancellations and refunds?
Assumption Documentation Template
Assumptions:
✓ System supports single city initially
✓ Payment integration is out of scope
✓ Focus on movie booking, not events
✓ Basic user authentication required
✓ Real-time seat availability needed
2. Identifying Entities, Behaviors, and Relationships
The NBR (Noun-Behavior-Relationship) Analysis
Step 1: Extract Nouns (Entities)
Read the problem statement and identify all nouns:
Primary entities: Core business objects (User, Movie, Theater)
// Bad: Multiple responsibilitiesclassUser{privateStringname;publicvoidsaveToDatabase(){}publicvoidsendEmail(){}publicvoidgenerateReport(){}}// Good: Single responsibilityclassUser{privateStringname;// Only user-related data and behavior}classUserRepository{publicvoidsaveUser(Useruser){}}classEmailService{publicvoidsendWelcomeEmail(Useruser){}}
Open/Closed Principle (OCP)
// Bad: Modifying existing codeclassPriceCalculator{publicdoublecalculatePrice(Seatseat){if(seat.getType()==SeatType.REGULAR){return100.0;}elseif(seat.getType()==SeatType.VIP){return200.0;}// Adding new seat type requires modification}}// Good: Open for extension, closed for modificationinterfacePricingStrategy{doublecalculatePrice(Seatseat);}classRegularSeatPricingimplementsPricingStrategy{publicdoublecalculatePrice(Seatseat){return100.0;}}classVIPSeatPricingimplementsPricingStrategy{publicdoublecalculatePrice(Seatseat){return200.0;}}
Liskov Substitution Principle (LSP)
// Good: Subclasses can replace base classabstractclassSeat{publicabstractbooleanisAvailable();publicabstractdoublegetPrice();}classRegularSeatextendsSeat{publicbooleanisAvailable(){return!isBooked;}publicdoublegetPrice(){return100.0;}}// Can use any Seat subclasspublicvoidbookSeat(Seatseat){if(seat.isAvailable()){// Book the seat}}
// Bad: High-level module depends on low-level moduleclassBookingService{privateMySQLDatabasedatabase=newMySQLDatabase();publicvoidsaveBooking(Bookingbooking){database.save(booking);}}// Good: Both depend on abstractioninterfaceDatabaseManager{voidsave(Bookingbooking);}classBookingService{privateDatabaseManagerdbManager;publicBookingService(DatabaseManagerdbManager){this.dbManager=dbManager;}publicvoidsaveBooking(Bookingbooking){dbManager.save(booking);}}
7. Design Patterns with Real Use-Case Triggers
Pattern Selection Matrix
Pattern
Use Case
Trigger Question
Strategy
Multiple algorithms
"How do we handle different pricing/payment methods?"
Factory
Object creation
"How do we create different types of users/seats?"
Observer
Event notifications
"How do we notify multiple systems about bookings?"
Singleton
Single instance
"Should we have only one instance of this?"
Template Method
Algorithm skeleton
"Do we have similar workflows with variations?"
Decorator
Add functionality
"How do we add features without modifying existing code?"
Common Patterns in LLD
Strategy Pattern
// Use when: Different algorithms for same operationinterfacePricingStrategy{doublecalculatePrice(Seatseat,Showshow);}classWeekdayPricingimplementsPricingStrategy{publicdoublecalculatePrice(Seatseat,Showshow){returnseat.getBasePrice()*0.8;// 20% discount}}classWeekendPricingimplementsPricingStrategy{publicdoublecalculatePrice(Seatseat,Showshow){returnseat.getBasePrice()*1.2;// 20% premium}}classPriceCalculator{privatePricingStrategystrategy;publicvoidsetPricingStrategy(PricingStrategystrategy){this.strategy=strategy;}publicdoublecalculatePrice(Seatseat,Showshow){returnstrategy.calculatePrice(seat,show);}}
Factory Pattern
// Use when: Creating different types of objectsinterfaceUserFactory{UsercreateUser(UserTypetype,Stringname,Stringemail);}classSimpleUserFactoryimplementsUserFactory{publicUsercreateUser(UserTypetype,Stringname,Stringemail){switch(type){caseREGULAR:returnnewRegularUser(name,email);casePREMIUM:returnnewPremiumUser(name,email);caseADMIN:returnnewAdminUser(name,email);default:thrownewIllegalArgumentException("Unknown user type");}}}
Observer Pattern
// Use when: Multiple objects need to be notified of changesinterfaceBookingObserver{voidonBookingCreated(Bookingbooking);voidonBookingCancelled(Bookingbooking);}classEmailNotificationServiceimplementsBookingObserver{publicvoidonBookingCreated(Bookingbooking){// Send confirmation email}publicvoidonBookingCancelled(Bookingbooking){// Send cancellation email}}classBookingService{privateList<BookingObserver>observers=newArrayList<>();publicvoidaddObserver(BookingObserverobserver){observers.add(observer);}publicvoidcreateBooking(BookingRequestrequest){Bookingbooking=processBooking(request);notifyObservers(booking,"CREATED");}privatevoidnotifyObservers(Bookingbooking,Stringaction){for(BookingObserverobserver:observers){if("CREATED".equals(action)){observer.onBookingCreated(booking);}}}}
8. Class Diagram Generation with UML
UML Notation Guide
Class Representation
┌─────────────────────┐
│ ClassName │
├─────────────────────┤
│ - attribute: type │
│ + attribute: type │
├─────────────────────┤
│ + method(): type │
│ - method(): type │
└─────────────────────┘
9. Designing for Extensibility, Testability, and Maintainability
The ETM Framework
Extensibility
Use interfaces and abstract classes for future implementations
Apply Strategy pattern for algorithm variations
Use configuration instead of hard-coded values
Design for plugin architecture when appropriate
// Extensible designinterfacePaymentProcessor{PaymentResultprocess(PaymentRequestrequest);}classPaymentService{privateMap<PaymentType,PaymentProcessor>processors;publicPaymentResultprocessPayment(PaymentRequestrequest){PaymentProcessorprocessor=processors.get(request.getType());returnprocessor.process(request);}// Easy to add new payment methodspublicvoidaddPaymentProcessor(PaymentTypetype,PaymentProcessorprocessor){processors.put(type,processor);}}
Testability
Use dependency injection for external dependencies
Create interfaces for mockable components
Keep methods small and focused
Avoid static methods and global state
// Testable designclassBookingService{privateDatabaseManagerdbManager;privateNotificationServicenotificationService;// Constructor injection for testingpublicBookingService(DatabaseManagerdbManager,NotificationServicenotificationService){this.dbManager=dbManager;this.notificationService=notificationService;}publicBookingResultcreateBooking(BookingRequestrequest){// Easily mockable dependenciesif(dbManager.isAvailable(request.getSeats())){Bookingbooking=newBooking(request);dbManager.save(booking);notificationService.notify(booking);returnBookingResult.success(booking);}returnBookingResult.failure("Seats not available");}}
Maintainability
Follow naming conventions consistently
Keep classes focused (Single Responsibility)
Use meaningful variable names
Add proper error handling
Document complex logic
// Maintainable designpublicclassSeatAvailabilityChecker{privatestaticfinalintMAX_SEATS_PER_BOOKING=10;publicAvailabilityResultcheckAvailability(List<Seat>requestedSeats,Showshow){if(requestedSeats.isEmpty()){returnAvailabilityResult.invalid("No seats requested");}if(requestedSeats.size()>MAX_SEATS_PER_BOOKING){returnAvailabilityResult.invalid("Too many seats requested");}List<Seat>unavailableSeats=findUnavailableSeats(requestedSeats,show);if(!unavailableSeats.isEmpty()){returnAvailabilityResult.unavailable(unavailableSeats);}returnAvailabilityResult.available();}privateList<Seat>findUnavailableSeats(List<Seat>seats,Showshow){// Implementation details}}
10. Simulating Key Flows to Validate Design
Flow Validation Checklist
Primary Flows
Happy path scenarios
Error scenarios
Edge cases
Concurrent scenarios
Example: Booking Flow Simulation
// Test Case: Successful bookingpublicvoidtestSuccessfulBooking(){// SetupUseruser=newUser("John","john@email.com");Showshow=newShow(movie,screen,DateTime.now());List<Seat>seats=Arrays.asList(seat1,seat2);// ExecuteBookingServiceservice=newBookingService(dbManager,notificationService);BookingResultresult=service.createBooking(newBookingRequest(user,show,seats));// ValidateassertTrue(result.isSuccess());assertEquals(BookingStatus.CONFIRMED,result.getBooking().getStatus());verify(dbManager).save(any(Booking.class));verify(notificationService).notify(any(Booking.class));}// Test Case: Concurrent bookingpublicvoidtestConcurrentBooking(){// Two users trying to book same seats simultaneously// Should handle race conditions properly}
Flow Scenarios to Test
Scenario
Expected Behavior
Validation Points
Happy Path
User successfully books available seats
Booking created, payment processed, notification sent
Main service methods: createBooking, cancelBooking
Supporting classes: Request/Response DTOs
Error handling: Basic validation and error responses
Design patterns: Only if time permits
12. Final Design Checklist
Pre-Submission Validation
✅ Requirement Compliance
[ ] All functional requirements addressed
[ ] Assumptions clearly documented
[ ] Edge cases identified and handled
✅ Design Quality
[ ] SOLID principles applied
[ ] Appropriate design patterns used
[ ] Clear separation of concerns
[ ] Proper abstraction levels
✅ Code Quality
[ ] Meaningful class and method names
[ ] Consistent naming conventions
[ ] Proper visibility modifiers
[ ] Appropriate data structures used
✅ Architecture
[ ] Layered architecture implemented
[ ] Dependencies properly managed
[ ] Interfaces used for abstraction
[ ] Extensibility considerations
✅ Error Handling
[ ] Input validation implemented
[ ] Exception handling strategy
[ ] Graceful failure modes
[ ] Proper error messages
✅ Testing Strategy
[ ] Unit testable design
[ ] Mock-friendly interfaces
[ ] Clear test scenarios identified
[ ] Validation points defined
13. Smart Clarifying Questions
The SCALE Framework
Scope Questions
"Should we focus on the core booking flow or include advanced features?"
"Are we designing for a single theater or multiple theaters?"
"Do we need to handle different user types (admin, customer, theater manager)?"
Constraints Questions
"What's the expected scale? (concurrent users, bookings per second)"
"Are there any technology constraints or preferences?"
"What's the acceptable response time for booking operations?"
Assumptions Questions
"Can we assume users are authenticated when making bookings?"
"Should we handle payment processing or just booking?"
"Are we supporting mobile and web platforms?"
Limitations Questions
"What's out of scope for this design?"
"Are there any features we should explicitly not implement?"
"Should we prioritize certain user flows over others?"
Edge Cases Questions
"How do we handle concurrent bookings for the same seat?"
"What happens if payment fails after seat reservation?"
"How do we handle system failures during booking?"
Question Categories by System Type
E-commerce/Booking Systems
"Do we need to handle inventory management?"
"How do we handle cancellations and refunds?"
"Are there different pricing tiers or promotions?"
Social Media/Chat Systems
"How do we handle message ordering and delivery?"
"Should we support group conversations?"
"Do we need to handle media attachments?"
Ride-sharing/Location Systems
"How do we handle driver matching algorithms?"
"Do we need real-time location tracking?"
"Should we support different vehicle types?"
Financial/Payment Systems
"How do we ensure transaction consistency?"
"Are there regulatory compliance requirements?"
"Do we need to handle multiple currencies?"
Quick Reference Card
30-Second Mental Checklist
Requirements: Clear? Assumptions documented?
Entities: All nouns identified? Relationships clear?
Architecture: Layered? Separation of concerns?
Classes: Single responsibility? Proper visibility?
Patterns: Right pattern for right problem?
SOLID: Principles followed?
Extensibility: Easy to add new features?
Testing: Mockable? Testable design?
Flows: Key scenarios validated?
Quality: Clean code? Good naming?
Common Pitfalls to Avoid
❌ Starting to code without clarifying requirements
❌ Creating god classes with too many responsibilities
❌ Ignoring error handling and edge cases
❌ Over-engineering with unnecessary patterns
❌ Not considering concurrent access scenarios
❌ Forgetting to validate the design with key flows
❌ Using inappropriate data structures
❌ Not considering future extensibility
Success Indicators
✅ Interviewer asks follow-up questions about design choices
✅ You can easily explain trade-offs and alternatives
✅ Design can handle new requirements with minimal changes
✅ Code is readable and self-documenting
✅ You can walk through complex scenarios confidently
Remember: The goal is not to create the perfect design, but to demonstrate systematic thinking, solid engineering principles, and the ability to make reasonable trade-offs under time constraints.
Top comments (0)
Subscribe
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Top comments (0)