Loading content...
After noun extraction and entity/attribute decisions, you'll have a list of candidate entities. But here's a critical question that separates experienced designers from beginners:
Should you model all of them?
The answer is almost always no—at least not immediately. Software design is about trade-offs, and one of the most important is scope management. Every entity you add increases complexity, development time, testing burden, and maintenance cost.
The over-design trap:
Beginners often try to model everything at once. They see a requirement mentioning 'notifications' and immediately design a complete notification system with templates, channels, preferences, and retry logic. But the core problem might work perfectly with a simple email sent from the main application.
Learning to distinguish essential entities (must-have for core functionality) from optional entities (nice-to-have enhancements) is a key skill for pragmatic design.
By the end of this page, you will understand how to categorize entities by importance, apply the minimal viable design principle, and make principled decisions about what to include in your initial design versus what to defer for future iterations.
Essential entities are the irreducible core of your system—without them, the system cannot fulfill its primary purpose. Optional entities extend, enhance, or optimize the system but aren't strictly necessary for basic operation.
Essential Entity Characteristics:
Optional Entity Characteristics:
| Aspect | Essential Entity | Optional Entity |
|---|---|---|
| Requirement link | Explicit in core requirements | Implicit or in secondary requirements |
| System impact if removed | System fails to deliver value | System less efficient/convenient |
| User visibility | Users directly interact with it | Often behind-the-scenes |
| Development priority | MVP, Phase 1, Sprint 1 | Later phases, backlog |
| Design effort | Full design attention | Sketch now, detail later |
| Examples (Parking Lot) | ParkingSpot, Vehicle, Ticket | DisplayBoard, Notification, Report |
Ask yourself: 'If I removed this entity, could a user still complete the core workflow?' If yes, it's optional. If no, it's essential. For a parking lot: without ParkingSpot, cars can't park. Without DisplayBoard, cars can still park—they just won't see available counts. Essential vs Optional.
The Minimal Viable Design (MVD) principle states:
Design only the entities necessary to satisfy the core requirements. Defer optional entities until they're needed.
This isn't laziness—it's strategic. Every entity you add carries hidden costs:
The MVD approach:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
// MINIMAL VIABLE DESIGN: Parking Lot System // ============================================// CORE USE CASE: Vehicle parks and pays// ============================================// 1. Vehicle arrives at parking lot// 2. System assigns a spot based on vehicle size// 3. Ticket is issued with entry time// 4. Vehicle parks in assigned spot// 5. When leaving, fee is calculated based on duration// 6. Payment is processed// 7. Vehicle exits // ESSENTIAL ENTITIES (required for core use case):// ✅ ParkingLot - the aggregate, knows its spots// ✅ Floor - organizes spots, tracks per-floor availability// ✅ ParkingSpot - the fundamental unit, has size and status// ✅ Vehicle - the entity being parked, has size// ✅ Ticket - tracks the parking session, links vehicle to spot// ✅ Payment - records the transaction // OPTIONAL ENTITIES (enhance but not required):// 🔸 DisplayBoard - shows availability (user convenience)// 🔸 Admin - manages lot (operational feature)// 🔸 ChargingStation - EV charging (special feature)// 🔸 Reservation - advanced booking (future feature)// 🔸 MembershipCard - loyalty program (business feature)// 🔸 Notification - alerts (engagement feature) // MVD focuses on essential entities: class ParkingLot { id: string; floors: Floor[]; findAvailableSpot(vehicle: Vehicle): ParkingSpot | null { for (const floor of this.floors) { const spot = floor.findSpotFor(vehicle); if (spot) return spot; } return null; } issueTicket(vehicle: Vehicle): Ticket { const spot = this.findAvailableSpot(vehicle); if (!spot) throw new Error("No available spots"); return Ticket.create(vehicle, spot); }} class Floor { id: string; floorNumber: number; spots: ParkingSpot[]; findSpotFor(vehicle: Vehicle): ParkingSpot | null { return this.spots.find(spot => spot.isAvailable() && spot.canFit(vehicle) ) || null; }} class ParkingSpot { id: string; size: SpotSize; status: SpotStatus; isAvailable(): boolean { return this.status === SpotStatus.AVAILABLE; } canFit(vehicle: Vehicle): boolean { return vehicle.size <= this.size; } occupy(): void { this.status = SpotStatus.OCCUPIED; } release(): void { this.status = SpotStatus.AVAILABLE; }} class Vehicle { licensePlate: string; size: VehicleSize;} class Ticket { id: string; vehicle: Vehicle; spot: ParkingSpot; entryTime: Date; exitTime: Date | null; static create(vehicle: Vehicle, spot: ParkingSpot): Ticket { spot.occupy(); return new Ticket(vehicle, spot, new Date()); } calculateFee(rateCard: RateCard): Money { const duration = this.getDurationHours(); return rateCard.calculateFee(this.vehicle.size, duration); }} class Payment { id: string; ticketId: string; amount: Money; method: PaymentMethod; status: PaymentStatus; paidAt: Date;} // These entities are complete enough for MVD.// Optional entities are noted but not designed in detail.Minimal Viable Design doesn't mean cutting corners on essential entities. The essential entities should be designed thoroughly with proper relationships, behaviors, and invariants. MVD means you don't spend design effort on entities that aren't needed yet.
Beyond the simple essential/optional binary, a more nuanced categorization helps with planning:
Tier 1: Core Entities
Tier 2: Supporting Entities
Tier 3: Enhancement Entities
Tier 4: Future Entities
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
// TIERED ENTITY ANALYSIS: Library Management System /** * TIER 1: CORE ENTITIES * Required for: Members can borrow and return books */ class Book { isbn: string; title: string; author: string; // Core functionality: what's in the library} class BookCopy { id: string; bookIsbn: string; status: CopyStatus; // AVAILABLE, BORROWED, LOST // Core functionality: track individual copies for lending} class Member { id: string; name: string; membershipStatus: MembershipStatus; // Core functionality: who can borrow} class Loan { id: string; copyId: string; memberId: string; borrowDate: Date; dueDate: Date; returnDate: Date | null; // Core functionality: tracks what's borrowed by whom} /** * TIER 2: SUPPORTING ENTITIES * Required for: Practical library operations */ class Librarian { id: string; name: string; permissions: Permission[]; // Supporting: manages library, not required for lending} class Fine { id: string; loanId: string; amount: Money; status: FineStatus; // Supporting: enforces return policy} class LibraryCard { cardNumber: string; memberId: string; issueDate: Date; expiryDate: Date; // Supporting: physical/digital identification} /** * TIER 3: ENHANCEMENT ENTITIES * Improve experience but not essential */ class Reservation { id: string; bookIsbn: string; memberId: string; requestDate: Date; status: ReservationStatus; // Enhancement: allows queuing for unavailable books} class Notification { id: string; recipientId: string; type: NotificationType; sentAt: Date; // Enhancement: reminders and alerts} class Review { id: string; bookIsbn: string; memberId: string; rating: number; comment: string; // Enhancement: social/discovery features} /** * TIER 4: FUTURE ENTITIES * Anticipated but not in current scope */ // class ReadingList { } // Members save books to read later// class BookClub { } // Groups of members with shared reading// class Recommendation { } // ML-based book suggestions// class DigitalCopy { } // E-book lending integration// class InterLibraryLoan { } // Borrowing from other libraries // These are noted in design documents but not designed yet.| Tier | When to Design | When to Implement | Documentation Level |
|---|---|---|---|
| Tier 1: Core | Immediately, in full detail | Sprint 1/MVP | Full class design, relationships, methods |
| Tier 2: Supporting | Early, with clear interfaces | Sprint 2-3 | Key attributes and main behaviors |
| Tier 3: Enhancement | When requirements stabilize | Later phases | Entity name and purpose only |
| Tier 4: Future | Don't design yet | Future roadmap | Just a note that it might exist |
How do you determine which tier an entity belongs to? The answer lies in analyzing the requirements document carefully. Requirements often contain linguistic clues about priority.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
# Elevator System - Requirements Analysis ## Requirement Analysis for Entity Prioritization ### REQUIREMENT 1 (Core - Tier 1)"The system **must** move elevators between floors to transport passengers." Entities involved:- Elevator ✓ Tier 1- Floor ✓ Tier 1- (Passenger implied but may be out of scope - just requests) ### REQUIREMENT 2 (Core - Tier 1) "Passengers **will** request elevators by pressing call buttons on each floor." Entities involved:- FloorCallButton (or CallRequest) ✓ Tier 1- Reinforces Floor as Tier 1 ### REQUIREMENT 3 (Core - Tier 1)"Passengers inside the elevator **shall** select destination floors." Entities involved:- ElevatorPanel / Destination Request ✓ Tier 1- Destination selection mechanism ### REQUIREMENT 4 (Supporting - Tier 2)"The system **should** optimize elevator movement to minimize wait times." Entities involved:- Scheduler / Algorithm (not an entity, but a component)- Metrics / Statistics ✓ Tier 2- May not need entity if embedded in Elevator logic ### REQUIREMENT 5 (Enhancement - Tier 3)"The system **may** support VIP floors with priority access." Entities involved:- VIPConfiguration ✓ Tier 3- AccessControl ✓ Tier 3 ### REQUIREMENT 6 (Future - Tier 4)"In **phase 2**, the system will support smart scheduling based on historical patterns." Entities involved:- UsageHistory ✓ Tier 4- PredictionModel ✓ Tier 4- Not designed now --- ## Resulting Entity Tiers | Tier | Entities | Justification ||------|----------|---------------|| 1 | Elevator, Floor, Request, ElevatorController | Core movement functionality || 2 | MaintenanceMode, EmergencyProtocol | Operational safety || 3 | VIPAccess, DisplayScreen | Enhanced features || 4 | UsageAnalytics, SmartScheduler | Phase 2 features |Sometimes core requirements are so obvious they're not stated. 'The parking lot must have parking spots' might not appear in requirements because it's assumed. Always verify that unstated assumptions don't hide essential entities.
In LLD interviews, time is limited (typically 45-60 minutes). Distinguishing essential from optional entities becomes even more critical. The interviewer wants to see that you can:
Interview-specific strategies:
12345678910111213141516171819202122232425262728293031323334
# Sample Interview Dialogue: Prioritizing Entities INTERVIEWER: "Design a movie ticket booking system." CANDIDATE: "Before I start designing, let me identify the core entities I'll focus on first. For a ticket booking system, the essential entities are:1. Movie - what's being shown2. Theatre/Screen - where it's shown 3. Show - specific showing (movie + screen + time)4. Seat - what's being booked5. Booking - the customer's reservation6. Payment - the transaction Secondary entities I'm aware of but will park for now:- User accounts and authentication- Reviews and ratings- Loyalty programs- Promotions and discounts Is this scope appropriate, or should I include any of the secondary features?" INTERVIEWER: "Focus on the core booking flow for now." CANDIDATE: "Perfect. Let me design these core entities in detail, starting with their relationships and key behaviors..." --- This approach demonstrates:✓ Clear thinking about priorities✓ Explicit about what's included and excluded✓ Seeks confirmation before diving deep✓ Shows awareness of broader systemDeferring optional entities doesn't mean ignoring them. Smart design anticipates future extensions by leaving extension points in the core design.
The Open-Closed Principle (OCP) in action:
Software entities should be open for extension but closed for modification.
When you design essential entities, consider:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
// DESIGNING WITH EXTENSION POINTS // CORE ENTITY: Payment// Designing with future payment methods in mind // ❌ Without extension thinkingclass PaymentBad { processCash(amount: number): void { // Cash logic } processCreditCard(cardNumber: string, amount: number): void { // Credit card logic } // Adding mobile payment requires modifying this class // Adding crypto payment requires modifying this class // Violates OCP} // ✅ With extension thinking: Strategy Patterninterface PaymentProcessor { process(payment: Payment): PaymentResult; supports(method: PaymentMethod): boolean;} class CashPaymentProcessor implements PaymentProcessor { process(payment: Payment): PaymentResult { // Cash logic } supports(method: PaymentMethod): boolean { return method === PaymentMethod.CASH; }} class CreditCardProcessor implements PaymentProcessor { process(payment: Payment): PaymentResult { // Credit card logic } supports(method: PaymentMethod): boolean { return method === PaymentMethod.CREDIT_CARD; }} // Future: Add new processor without modifying existing codeclass MobilePayProcessor implements PaymentProcessor { process(payment: Payment): PaymentResult { /* ... */ } supports(method: PaymentMethod): boolean { return method === PaymentMethod.MOBILE; }} class PaymentService { private processors: PaymentProcessor[]; processPayment(payment: Payment): PaymentResult { const processor = this.processors.find(p => p.supports(payment.method) ); if (!processor) throw new Error("Unsupported payment method"); return processor.process(payment); }} // CORE ENTITY: Notification (noted as optional, designed minimally)// Leave extension point for when it becomes needed // ❌ Without extension thinking: hardcoded to emailclass TicketBad { complete(): void { this.status = TicketStatus.COMPLETED; this.sendEmail(); // Hardcoded, hard to extend }} // ✅ With extension thinking: Event-basedinterface DomainEvent { type: string; payload: any; occurredAt: Date;} class TicketCompletedEvent implements DomainEvent { type = 'TicketCompleted'; constructor( public payload: { ticketId: string; amount: Money }, public occurredAt: Date = new Date() ) {}} class Ticket { private events: DomainEvent[] = []; complete(): void { this.status = TicketStatus.COMPLETED; // Raise event instead of direct action this.events.push(new TicketCompletedEvent({ ticketId: this.id, amount: this.totalAmount })); } getUncommittedEvents(): DomainEvent[] { return this.events; }} // Later, when Notification entity is implemented:class NotificationListener { handle(event: TicketCompletedEvent): void { // Send notification // No changes to Ticket class needed! }}Extension points are valuable, but don't create them speculatively. Apply YAGNI (You Aren't Gonna Need It). Add extension points when you can reasonably anticipate the extension—like multiple payment methods—not for every possible future feature.
Let's apply entity prioritization to a complete example: an E-Commerce system. This demonstrates how to work from requirements to a tiered entity list.
1234567891011121314151617181920212223242526
# E-Commerce Platform Requirements ## Core Requirements (MVP)1. Customers browse products organized in categories2. Customers add products to a shopping cart3. Customers checkout with shipping and payment information4. Orders are placed and customers receive confirmation5. Inventory is updated when orders are placed ## Secondary Requirements6. Customers can create accounts to save information7. Customers can view order history8. Sellers can list and manage their products9. Products have reviews and ratings ## Nice-to-Have Features10. Wishlist functionality11. Product recommendations based on browsing12. Promotional discounts and coupon codes13. Multiple shipping options with different speeds/prices ## Future Considerations14. Subscription products (monthly boxes)15. Digital product delivery16. Seller analytics dashboard17. Fraud detection system| Tier | Entity | Source Requirement | Justification |
|---|---|---|---|
| 1 | Product | Req 1: browse products | Core: what's being sold |
| 1 | Category | Req 1: organized in categories | Core: navigation structure |
| 1 | Cart | Req 2: shopping cart | Core: purchase flow |
| 1 | CartItem | Req 2: add products | Core: cart contents |
| 1 | Order | Req 4: orders placed | Core: transaction record |
| 1 | OrderItem | Implied by Order | Core: order contents |
| 1 | InventoryItem | Req 5: inventory updated | Core: stock tracking |
| 2 | Customer | Req 6: create accounts | Supporting: user management |
| 2 | Address | Req 3: shipping info | Supporting: delivery details |
| 2 | Payment | Req 3: payment info | Supporting: transaction detail |
| 2 | Seller | Req 8: sellers list products | Supporting: marketplace model |
| 3 | Review | Req 9: reviews and ratings | Enhancement: social proof |
| 3 | Wishlist | Req 10: wishlist | Enhancement: convenience |
| 3 | Promotion | Req 12: discounts | Enhancement: marketing |
| 3 | ShippingOption | Req 13: shipping options | Enhancement: choice |
| 4 | Subscription | Req 14: subscription | Future: new model |
| 4 | DigitalDelivery | Req 15: digital products | Future: new product type |
| 4 | Analytics | Req 16: dashboard | Future: insights |
| 4 | FraudCheck | Req 17: fraud detection | Future: security |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
// E-COMMERCE: TIER 1 ENTITIES (Detailed Design) class Product { id: ProductId; sellerId: SellerId; name: string; description: string; price: Money; categoryId: CategoryId; status: ProductStatus; // DRAFT, ACTIVE, DISCONTINUED isAvailable(): boolean { return this.status === ProductStatus.ACTIVE; }} class Category { id: CategoryId; name: string; parentId: CategoryId | null; isRoot(): boolean { return this.parentId === null; }} class Cart { id: CartId; customerId: CustomerId | null; // Null for guest carts items: CartItem[]; createdAt: Date; updatedAt: Date; addItem(product: Product, quantity: number): void { const existing = this.items.find(i => i.productId === product.id); if (existing) { existing.updateQuantity(existing.quantity + quantity); } else { this.items.push(new CartItem(product.id, quantity, product.price)); } this.updatedAt = new Date(); } removeItem(productId: ProductId): void { this.items = this.items.filter(i => i.productId !== productId); } calculateTotal(): Money { return this.items.reduce( (sum, item) => sum.add(item.subtotal()), Money.zero() ); } toOrder(shippingAddress: Address, paymentDetails: PaymentDetails): Order { return Order.createFromCart(this, shippingAddress, paymentDetails); }} class CartItem { productId: ProductId; quantity: number; unitPrice: Money; // Captured at add time subtotal(): Money { return this.unitPrice.multiply(this.quantity); } updateQuantity(newQuantity: number): void { if (newQuantity < 1) throw new Error("Quantity must be at least 1"); this.quantity = newQuantity; }} class Order { id: OrderId; customerId: CustomerId; items: OrderItem[]; shippingAddress: Address; totalAmount: Money; status: OrderStatus; placedAt: Date; static createFromCart( cart: Cart, shippingAddress: Address, paymentDetails: PaymentDetails ): Order { // Transform cart to order // Capture snapshot of prices // Set status to PENDING } confirm(): void { this.status = OrderStatus.CONFIRMED; // Trigger inventory reservation } ship(trackingNumber: string): void { this.status = OrderStatus.SHIPPED; }} class InventoryItem { id: InventoryId; productId: ProductId; quantity: number; reservedQuantity: number; availableQuantity(): number { return this.quantity - this.reservedQuantity; } reserve(amount: number): void { if (amount > this.availableQuantity()) { throw new Error("Insufficient inventory"); } this.reservedQuantity += amount; } confirmReservation(amount: number): void { this.reservedQuantity -= amount; this.quantity -= amount; } releaseReservation(amount: number): void { this.reservedQuantity -= amount; }}You now understand how to prioritize entities, focusing design effort on what matters most while maintaining awareness of the broader system.
What's next:
After identifying entities and categorizing them by priority, you need to verify your entity list is correct and complete. The next page covers entity list validation—techniques to check that you haven't missed essential entities or included concepts that shouldn't be entities.
You can now distinguish essential from optional entities, prioritize your design effort appropriately, and avoid the over-design trap that slows down development. Next, we'll learn validation techniques to ensure your entity list is correct and complete.