Loading content...
No one builds a house by completing one room entirely—walls, electrical, plumbing, paint, furniture—before starting the next. That approach would be inefficient, inflexible, and would hide problems until too late.
Instead, builders work in layers: foundation for the entire house, then framing for all rooms, then electrical throughout, then plumbing, then drywall, then finishes. Each layer is complete and inspected before the next begins.
Incremental implementation applies this same wisdom to software. Rather than completing one class before starting the next, you build the system in functional layers—each one working, tested, and validated before adding the next.
This approach is essential for:
By the end of this page, you will understand the incremental implementation philosophy, techniques for identifying implementation layers, strategies for maintaining a working system at each step, how to prioritize which functionality to implement first, and how to apply incremental implementation under interview time pressure.
Incremental development isn't about writing less code—it's about writing code in an order that maximizes value and minimizes risk at every step.
The Core Insight:
At any point during development, you should have a working system that does something useful. This "something" might be limited, but it's functional and correct. Each increment extends what the system can do.
Contrast with Big-Bang Development:
| Aspect | Big-Bang Approach | Incremental Approach |
|---|---|---|
| First working system | After all code is written | After first increment |
| Error discovery | At the end (expensive to fix) | Immediately (cheap to fix) |
| Stakeholder feedback | After completion | After each increment |
| Risk exposure | High throughout, drops suddenly at end | Continuously decreasing |
| Team morale | Low until completion | Steady progress visible |
| Flexibility | Committed to original plan | Can pivot based on learning |
In interviews, incrementalism is essential. A candidate who shows a working parking lot system that handles cars (but not trucks or motorcycles) will score higher than one who has half-implemented code for all vehicle types. Working code demonstrates competence; incomplete code raises questions.
There are two primary strategies for incremental implementation:
Horizontal Layers: Build entire infrastructure layers before functionality
Vertical Slices: Build complete user flows, one at a time
The Modern Preference: Vertical Slices
Vertical slices are generally preferred because they produce working functionality faster. After the first slice, you have a usable system. With horizontal layers, you have usable code only after all layers are complete.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
// ================================================// Scenario: Building a Library Management System// Features: Browse books, Checkout book, Return book, Manage members// ================================================ // ================================================// HORIZONTAL APPROACH (Not Recommended)// ================================================ // Week 1: All modelsclass Book { /* ... */ }class Member { /* ... */ }class Loan { /* ... */ }class Librarian { /* ... */ }class Fine { /* ... */ }class Reservation { /* ... */ } // Week 2: All repositoriesclass BookRepository { /* ... */ }class MemberRepository { /* ... */ }class LoanRepository { /* ... */ }// ... etc // Week 3: All servicesclass BookService { /* ... */ }class MemberService { /* ... */ }class LoanService { /* ... */ }// ... etc // Week 4: Finally, a working feature!// Problem: No working system until Week 4// Problem: Errors in Week 1 models discovered in Week 4 // ================================================// VERTICAL SLICE APPROACH (Recommended)// ================================================ // --- SLICE 1: Browse Books (simplest feature) --- // Just enough modelclass Book { constructor( private readonly isbn: string, private title: string, private author: string ) {} getIsbn(): string { return this.isbn; } getTitle(): string { return this.title; } getAuthor(): string { return this.author; }} // Just enough repositoryinterface BookRepository { findAll(): Promise<Book[]>; findByIsbn(isbn: string): Promise<Book | null>; searchByTitle(query: string): Promise<Book[]>;} class InMemoryBookRepository implements BookRepository { private books: Book[] = []; async findAll(): Promise<Book[]> { return [...this.books]; } async findByIsbn(isbn: string): Promise<Book | null> { return this.books.find(b => b.getIsbn() === isbn) || null; } async searchByTitle(query: string): Promise<Book[]> { const lowerQuery = query.toLowerCase(); return this.books.filter(b => b.getTitle().toLowerCase().includes(lowerQuery) ); } // For seeding test data addBook(book: Book): void { this.books.push(book); }} // Just enough serviceclass BookBrowsingService { constructor(private bookRepo: BookRepository) {} async getAllBooks(): Promise<Book[]> { return this.bookRepo.findAll(); } async searchBooks(query: string): Promise<Book[]> { return this.bookRepo.searchByTitle(query); }} // ✅ CHECKPOINT: "Browse Books" feature is COMPLETE and WORKING// Can demo, can test, can get feedback// Total time: ~30 minutes // --- SLICE 2: Checkout Book --- // Extend Book modelclass BookV2 { private available: boolean = true; constructor( private readonly isbn: string, private title: string, private author: string ) {} isAvailable(): boolean { return this.available; } checkout(): void { this.available = false; } returnBook(): void { this.available = true; } // ... previous methods} // Add Member modelclass Member { constructor( private readonly id: string, private name: string, private email: string ) {} getId(): string { return this.id; } getName(): string { return this.name; }} // Add Loan modelclass Loan { constructor( private readonly id: string, private book: BookV2, private member: Member, private dueDate: Date, private checkoutDate: Date = new Date() ) {} getId(): string { return this.id; } getBook(): BookV2 { return this.book; } getMember(): Member { return this.member; } getDueDate(): Date { return this.dueDate; }} // Add repositoriesinterface MemberRepository { findById(id: string): Promise<Member | null>;} interface LoanRepository { save(loan: Loan): Promise<void>; findActiveByMember(memberId: string): Promise<Loan[]>;} // Add checkout serviceclass CheckoutService { constructor( private bookRepo: BookRepository, private memberRepo: MemberRepository, private loanRepo: LoanRepository ) {} async checkoutBook(isbn: string, memberId: string): Promise<Loan> { const book = await this.bookRepo.findByIsbn(isbn); if (!book) throw new Error("Book not found"); if (!book.isAvailable()) throw new Error("Book not available"); const member = await this.memberRepo.findById(memberId); if (!member) throw new Error("Member not found"); const dueDate = new Date(); dueDate.setDate(dueDate.getDate() + 14); // 2 weeks const loan = new Loan( `loan_${Date.now()}`, book, member, dueDate ); book.checkout(); await this.loanRepo.save(loan); return loan; }} // ✅ CHECKPOINT: "Checkout Book" feature is COMPLETE and WORKING// Browse + Checkout both work// Total time: ~1 hour // --- SLICE 3: Return Book ---// Extends Loan, adds return logic, adds fine calculation// Total time: ~30 minutes more // --- SLICE 4: Manage Members ---// CRUD for members// Total time: ~30 minutes more // ================================================// COMPARISON// ================================================ // Horizontal: 4 weeks until any feature works// Vertical: Working feature every 30-60 minutes// // With vertical slices:// - After 30 min: Can demo book browsing// - After 1 hour: Can demo checkout// - Errors in Book model found in Slice 1, not Slice 4// - Stakeholder can use partial system and give feedbackHorizontal layering is appropriate when infrastructure must be in place before any feature works (e.g., authentication, database setup), or when multiple features share common infrastructure that's expensive to retrofit. Even then, implement the minimal horizontal layer needed for the first vertical slice.
Not all increments are equal. Some provide more value, reduce more risk, or enable more subsequent work. Prioritization determines which increments to tackle first.
The Prioritization Framework:
Consider four factors:
Priority Matrix:
| Priority | Characteristics | Action | Examples |
|---|---|---|---|
| 1 - Critical | High value, many dependents, high risk | Do first | Core entities, primary user flow, main abstraction |
| 2 - Important | High value or many dependents | Do second | Secondary flows, key edge cases, integration points |
| 3 - Nice to Have | Moderate value, few dependents | Do if time permits | Additional features, UI polish, optimizations |
| 4 - Defer | Low value, high effort | Skip or stub | Rare edge cases, admin features, analytics |
The "Walking Skeleton" Approach:
A walking skeleton is the minimal end-to-end flow that exercises all layers of the system. It's the first increment in many projects:
The walking skeleton proves the architecture works. Everything else is incremental enhancement.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
// ================================================// Scenario: Elevator System// ================================================ // Identify all potential increments:const allIncrements = [ // Core functionality { name: "Single elevator moves up/down", priority: 1 }, { name: "Elevator responds to floor button", priority: 1 }, { name: "Elevator doors open/close", priority: 1 }, // Secondary functionality { name: "Multiple elevators coordinated", priority: 2 }, { name: "Optimal elevator selection", priority: 2 }, { name: "Emergency stop button", priority: 2 }, // Nice to have { name: "Display current floor", priority: 3 }, { name: "Destination dispatch system", priority: 3 }, { name: "Weight sensor", priority: 3 }, // Defer { name: "Maintenance mode", priority: 4 }, { name: "Analytics dashboard", priority: 4 }, { name: "Voice announcements", priority: 4 },]; // ================================================// INCREMENT 1: Walking Skeleton (Priority 1)// "Single elevator responds to button press"// ================================================ enum ElevatorState { IDLE = 'IDLE', MOVING_UP = 'MOVING_UP', MOVING_DOWN = 'MOVING_DOWN', DOORS_OPEN = 'DOORS_OPEN'} class Elevator { private currentFloor: number = 1; private state: ElevatorState = ElevatorState.IDLE; private destinationFloors: number[] = []; getCurrentFloor(): number { return this.currentFloor; } getState(): ElevatorState { return this.state; } requestFloor(floor: number): void { if (floor === this.currentFloor) { this.openDoors(); return; } if (!this.destinationFloors.includes(floor)) { this.destinationFloors.push(floor); this.destinationFloors.sort((a, b) => a - b); } this.processNextDestination(); } private processNextDestination(): void { if (this.destinationFloors.length === 0) { this.state = ElevatorState.IDLE; return; } const nextFloor = this.getNextDestination(); if (nextFloor > this.currentFloor) { this.moveUp(); } else if (nextFloor < this.currentFloor) { this.moveDown(); } } private getNextDestination(): number { // Simple: go to closest return this.destinationFloors[0]; } private moveUp(): void { this.state = ElevatorState.MOVING_UP; // In real system, this would be async this.currentFloor++; this.checkArrival(); } private moveDown(): void { this.state = ElevatorState.MOVING_DOWN; this.currentFloor--; this.checkArrival(); } private checkArrival(): void { if (this.destinationFloors.includes(this.currentFloor)) { this.destinationFloors = this.destinationFloors.filter( f => f !== this.currentFloor ); this.openDoors(); } else { this.processNextDestination(); } } private openDoors(): void { this.state = ElevatorState.DOORS_OPEN; // Doors auto-close after delay setTimeout(() => this.closeDoors(), 3000); } private closeDoors(): void { this.processNextDestination(); }} // ✅ INCREMENT 1 COMPLETE// Can demo: Single elevator moving between floors// Test: Request floors, verify arrival // ================================================// INCREMENT 2: Multiple Elevators (Priority 2)// ================================================ class ElevatorController { private elevators: Elevator[] = []; constructor(elevatorCount: number) { for (let i = 0; i < elevatorCount; i++) { this.elevators.push(new Elevator()); } } requestElevator(fromFloor: number): void { const bestElevator = this.selectBestElevator(fromFloor); bestElevator.requestFloor(fromFloor); } private selectBestElevator(targetFloor: number): Elevator { // Simple: select closest idle elevator, or closest overall let bestElevator = this.elevators[0]; let bestDistance = Math.abs( bestElevator.getCurrentFloor() - targetFloor ); for (const elevator of this.elevators) { const distance = Math.abs(elevator.getCurrentFloor() - targetFloor); // Prefer idle elevators if (elevator.getState() === ElevatorState.IDLE && distance <= bestDistance) { bestElevator = elevator; bestDistance = distance; } } return bestElevator; }} // ✅ INCREMENT 2 COMPLETE// Can demo: Multiple elevators, basic selection// Builds on increment 1 // ================================================// INCREMENT 3: Optimal Selection (Priority 2)// ================================================ class SmartElevatorController extends ElevatorController { // Override selection with smarter algorithm private selectBestElevator(targetFloor: number): Elevator { // SCAN algorithm: prefer elevators moving toward the target // Implementation details... return super.selectBestElevator(targetFloor); }} // ✅ INCREMENT 3 COMPLETE// Enhanced selection algorithm// Increment 1 & 2 still work // Continue with priority 2, then 3...// Priority 4 items are stubbed or mentioned but not implementedThe key discipline of incremental development is ensuring the system works after every change. This requires deliberate practices:
The Green-Bar Rule:
After every increment:
If any of these fail, you haven't completed the increment—you've introduced a bug.
Techniques for Maintaining Working State:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
// ================================================// Example: Adding Features While Keeping System Working// ================================================ // --- INITIAL WORKING STATE --- interface PricingStrategy { calculatePrice(durationMinutes: number): number;} class FlatRatePricing implements PricingStrategy { constructor(private ratePerHour: number) {} calculatePrice(durationMinutes: number): number { const hours = Math.ceil(durationMinutes / 60); return hours * this.ratePerHour; }} class ParkingTicketService { constructor(private pricing: PricingStrategy) {} calculateFee(ticket: Ticket): number { const minutes = this.getDurationMinutes(ticket); return this.pricing.calculatePrice(minutes); } private getDurationMinutes(ticket: Ticket): number { const now = new Date(); const entry = ticket.getEntryTime(); return Math.floor((now.getTime() - entry.getTime()) / 60000); }} // ✅ Working: Flat rate pricing // --- ADD TIME-OF-DAY PRICING (without breaking existing) --- // New strategy implements same interfaceclass TimeOfDayPricing implements PricingStrategy { constructor( private dayRate: number, // 6am - 6pm private nightRate: number // 6pm - 6am ) {} calculatePrice(durationMinutes: number): number { // For simplicity, use current hour // Real implementation would prorate across time boundaries const hour = new Date().getHours(); const rate = (hour >= 6 && hour < 18) ? this.dayRate : this.nightRate; const hours = Math.ceil(durationMinutes / 60); return hours * rate; }} // ✅ Working: Both FlatRatePricing AND TimeOfDayPricing work// Existing code unchanged// Existing tests still pass // --- ADD VEHICLE-TYPE PRICING (extend interface carefully) --- // Option 1: New interface (safest)interface VehicleAwarePricing { calculatePrice(durationMinutes: number, vehicleType: VehicleType): number;} // Adapter to use new interface with existing serviceclass VehiclePricingAdapter implements PricingStrategy { constructor( private vehiclePricing: VehicleAwarePricing, private defaultVehicleType: VehicleType ) {} calculatePrice(durationMinutes: number): number { return this.vehiclePricing.calculatePrice( durationMinutes, this.defaultVehicleType ); }} // Option 2: Optional parameter (maintains backward compatibility)interface PricingStrategyV2 { calculatePrice(durationMinutes: number, vehicleType?: VehicleType): number;} class VehicleAwareFlatRate implements PricingStrategyV2 { constructor(private rates: Map<VehicleType, number>) {} calculatePrice(durationMinutes: number, vehicleType?: VehicleType): number { const type = vehicleType || VehicleType.CAR; // Default maintains compatibility const rate = this.rates.get(type) || this.rates.get(VehicleType.CAR)!; const hours = Math.ceil(durationMinutes / 60); return hours * rate; }} // ✅ Working: All previous pricing strategies STILL WORK// New vehicle-aware pricing also works// No breaking changes // --- FEATURE FLAG EXAMPLE --- class ParkingSystem { private useNewPricingEngine: boolean; constructor(config: SystemConfig) { this.useNewPricingEngine = config.featureFlags.newPricingEngine; } calculateFee(ticket: Ticket): number { if (this.useNewPricingEngine) { // New code path return this.newPricingEngine.calculate(ticket); } else { // Old code path - known working return this.legacyPricing.calculateFee(ticket); } }} // ✅ Working: New code exists but is disabled by default// Can enable for testing without affecting production// If new code has bugs, flag off instantlyOnce a system stops working ('I'll fix that later'), the pressure to keep it working disappears. Small breakages accumulate into large ones. Maintain the discipline: if you broke it, fix it before adding anything else. A working system is a prerequisite, not a goal.
In LLD interviews, time is extremely limited. Incremental implementation becomes not just helpful but essential. You simply cannot implement everything, so you must maximize the value of what you do implement.
The Interview Reality:
The Strategy: Core Flow First
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
// ================================================// Interview: Design a Parking Lot System (35 min implementation)// ================================================ // MINUTE 0-2: Identify core flow// "The core flow is: Vehicle enters → Gets ticket → Parks → // Pays → Exits. I'll implement this first." // MINUTE 2-7: Core entities (minimal)enum VehicleType { CAR, MOTORCYCLE, TRUCK }enum SpotSize { COMPACT, REGULAR, LARGE } class Vehicle { constructor( private licensePlate: string, private type: VehicleType ) {} getLicensePlate(): string { return this.licensePlate; } getType(): VehicleType { return this.type; }} class ParkingSpot { private vehicle: Vehicle | null = null; constructor( private id: string, private size: SpotSize ) {} isAvailable(): boolean { return this.vehicle === null; } park(vehicle: Vehicle): void { this.vehicle = vehicle; } vacate(): void { this.vehicle = null; } canFit(vehicleType: VehicleType): boolean { // Simple mapping if (vehicleType === VehicleType.MOTORCYCLE) return true; if (vehicleType === VehicleType.CAR) return this.size !== SpotSize.COMPACT; return this.size === SpotSize.LARGE; }} class Ticket { private exitTime: Date | null = null; constructor( private id: string, private vehicle: Vehicle, private spot: ParkingSpot, private entryTime: Date = new Date() ) {} getId(): string { return this.id; } getVehicle(): Vehicle { return this.vehicle; } getSpot(): ParkingSpot { return this.spot; } getEntryTime(): Date { return this.entryTime; } getExitTime(): Date | null { return this.exitTime; } markExit(): void { this.exitTime = new Date(); }} // MINUTE 7-15: Core service (parking flow)class ParkingLot { private spots: ParkingSpot[] = []; private activeTickets: Map<string, Ticket> = new Map(); private hourlyRate: number; constructor(hourlyRate: number) { this.hourlyRate = hourlyRate; this.initializeSpots(); } private initializeSpots(): void { // Create spots - simplified for (let i = 0; i < 50; i++) { this.spots.push(new ParkingSpot(`C${i}`, SpotSize.COMPACT)); } for (let i = 0; i < 100; i++) { this.spots.push(new ParkingSpot(`R${i}`, SpotSize.REGULAR)); } for (let i = 0; i < 20; i++) { this.spots.push(new ParkingSpot(`L${i}`, SpotSize.LARGE)); } } // Core operation 1: Entry enter(vehicle: Vehicle): Ticket { const spot = this.findAvailableSpot(vehicle.getType()); if (!spot) { throw new Error("No available spots"); } spot.park(vehicle); const ticket = new Ticket( `TKT-${Date.now()}`, vehicle, spot ); this.activeTickets.set(ticket.getId(), ticket); return ticket; } private findAvailableSpot(vehicleType: VehicleType): ParkingSpot | null { return this.spots.find(spot => spot.isAvailable() && spot.canFit(vehicleType) ) || null; } // Core operation 2: Calculate fee calculateFee(ticketId: string): number { const ticket = this.activeTickets.get(ticketId); if (!ticket) throw new Error("Ticket not found"); const now = new Date(); const hours = Math.ceil( (now.getTime() - ticket.getEntryTime().getTime()) / (1000 * 60 * 60) ); return hours * this.hourlyRate; } // Core operation 3: Exit exit(ticketId: string, paymentAmount: number): void { const ticket = this.activeTickets.get(ticketId); if (!ticket) throw new Error("Ticket not found"); const fee = this.calculateFee(ticketId); if (paymentAmount < fee) { throw new Error(`Insufficient payment. Required: ${fee}`); } ticket.markExit(); ticket.getSpot().vacate(); this.activeTickets.delete(ticketId); }} // ✅ MINUTE 15: CORE FLOW COMPLETE// Say: "Let me walk you through the core flow..."// Trace: Vehicle enters → Spot assigned → Ticket issued → // Fee calculated → Payment processed → Spot freed // MINUTE 15-20: Add one enhancement (if time)// "Now I'll add multiple floors..." class ParkingFloor { private spots: ParkingSpot[] = []; constructor( private floorNumber: number, compactCount: number, regularCount: number, largeCount: number ) { this.initializeSpots(compactCount, regularCount, largeCount); } private initializeSpots(compact: number, regular: number, large: number): void { const prefix = `F${this.floorNumber}`; for (let i = 0; i < compact; i++) { this.spots.push(new ParkingSpot(`${prefix}-C${i}`, SpotSize.COMPACT)); } for (let i = 0; i < regular; i++) { this.spots.push(new ParkingSpot(`${prefix}-R${i}`, SpotSize.REGULAR)); } for (let i = 0; i < large; i++) { this.spots.push(new ParkingSpot(`${prefix}-L${i}`, SpotSize.LARGE)); } } findAvailableSpot(vehicleType: VehicleType): ParkingSpot | null { return this.spots.find(spot => spot.isAvailable() && spot.canFit(vehicleType) ) || null; }} // ✅ MINUTE 20: Enhanced with floors // MINUTE 20-25: Payment system abstractioninterface PaymentProcessor { process(amount: number, method: PaymentMethod): PaymentResult;} class CashPayment implements PaymentProcessor { process(amount: number, method: PaymentMethod): PaymentResult { // Cash always succeeds return { success: true, transactionId: `CASH-${Date.now()}` }; }} class CardPayment implements PaymentProcessor { process(amount: number, method: PaymentMethod): PaymentResult { // Would call payment gateway return { success: true, transactionId: `CARD-${Date.now()}` }; }} // ✅ MINUTE 25: Payment abstraction in place // MINUTE 25-30: Walk through, answer questions// "What I haven't implemented but would in production:// - Multiple lot management// - Reserved spots// - Subscription pricing// - Real-time spot display// - Historical reporting// These would follow the same patterns I've shown." // MINUTE 30-35: Extension based on interviewer questions// "You asked about peak pricing? Here's how I would add that..." interface PricingStrategy { calculateFee(durationMinutes: number, vehicleType: VehicleType): number;} class PeakPricing implements PricingStrategy { constructor( private baseRate: number, private peakMultiplier: number, private peakHours: { start: number; end: number } ) {} calculateFee(durationMinutes: number, vehicleType: VehicleType): number { const currentHour = new Date().getHours(); const isPeak = currentHour >= this.peakHours.start && currentHour < this.peakHours.end; const rate = isPeak ? this.baseRate * this.peakMultiplier : this.baseRate; return Math.ceil(durationMinutes / 60) * rate; }} // ✅ MINUTE 35: Demonstrated extensibilityAfter implementing the core flow, pause and walk through it. Say: 'Let me trace through what happens when a vehicle enters...' and explain each step. This proves your code works, shows you can reason about it, and gives the interviewer confidence that the rest would work too.
Several patterns emerge repeatedly in incremental implementation. Recognizing them accelerates your work.
Pattern 1: Happy Path First
Implement the successful case before any error handling. A system that works for valid inputs is infinitely more valuable than one that rejects invalid inputs but doesn't process valid ones.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
// INCREMENT 1: Happy path onlyclass OrderService { placeOrder(order: Order): OrderResult { // Assume valid input, sufficient inventory, working payment this.inventory.reserve(order.items); this.payment.charge(order.total); return { success: true, orderId: order.id }; }} // INCREMENT 2: Add validationclass OrderServiceV2 { placeOrder(order: Order): OrderResult { // Validation layer if (!order.items.length) { throw new Error("Order must have items"); } if (order.total <= 0) { throw new Error("Order total must be positive"); } // Happy path (unchanged) this.inventory.reserve(order.items); this.payment.charge(order.total); return { success: true, orderId: order.id }; }} // INCREMENT 3: Add error recoveryclass OrderServiceV3 { placeOrder(order: Order): OrderResult { // Validation this.validate(order); // Try to reserve inventory const reserved = this.inventory.reserve(order.items); if (!reserved) { throw new InsufficientInventoryError(order.items); } // Try to charge, rollback on failure try { this.payment.charge(order.total); } catch (error) { this.inventory.release(order.items); // Rollback throw new PaymentFailedError(error); } return { success: true, orderId: order.id }; }}Pattern 2: Stub and Replace
Create placeholder implementations that allow the system to work, then replace them with real implementations incrementally.
1234567891011121314151617181920212223242526272829303132
// INCREMENT 1: Stub implementationsclass StubEmailService implements EmailService { async send(to: string, subject: string, body: string): Promise<void> { console.log(`[EMAIL STUB] To: ${to}, Subject: ${subject}`); // Does nothing - but system works! }} class StubPaymentGateway implements PaymentGateway { async charge(amount: number): Promise<PaymentResult> { console.log(`[PAYMENT STUB] Charging ${amount}`); return { success: true, transactionId: 'STUB-123' }; }} // System works end-to-end with stubsconst service = new OrderService( new StubEmailService(), new StubPaymentGateway()); // INCREMENT 2: Replace email with realconst serviceV2 = new OrderService( new SendGridEmailService(apiKey), // Real new StubPaymentGateway() // Still stub); // INCREMENT 3: Replace payment with realconst serviceV3 = new OrderService( new SendGridEmailService(apiKey), new StripePaymentGateway(stripeKey) // Real);Pattern 3: Single Variant First
When there are multiple similar classes (vehicle types, payment methods, notification channels), implement one fully, then replicate the pattern.
12345678910111213141516171819202122232425262728293031323334353637
// INCREMENT 1: Implement Car onlyclass Car extends Vehicle { constructor(licensePlate: string) { super(licensePlate, VehicleType.CAR); } getRequiredSpotSize(): SpotSize { return SpotSize.REGULAR; } getHourlyRate(): number { return 10; }} // System works with cars onlyconst parkingLot = new ParkingLot();parkingLot.enter(new Car("ABC-123")); // Works! // INCREMENT 2: Add motorcycle (copy pattern from Car)class Motorcycle extends Vehicle { constructor(licensePlate: string) { super(licensePlate, VehicleType.MOTORCYCLE); } getRequiredSpotSize(): SpotSize { return SpotSize.COMPACT; } getHourlyRate(): number { return 5; }} // INCREMENT 3: Add truck (copy pattern from Car) class Truck extends Vehicle { constructor(licensePlate: string) { super(licensePlate, VehicleType.TRUCK); } getRequiredSpotSize(): SpotSize { return SpotSize.LARGE; } getHourlyRate(): number { return 20; }} // In an interview, say:// "Motorcycle and Truck follow the same pattern as Car.// I've implemented Car fully; the others are templates."Once you recognize 'this is a stub-and-replace situation' or 'this is a single-variant-first case,' you can apply the pattern without thinking through first principles. These patterns become muscle memory with practice.
We've explored the philosophy and practice of incremental implementation. Here are the essential principles:
Congratulations! You've completed the 'From Design to Code' module. You now have a comprehensive framework for translating UML designs to production code, determining implementation order, applying interface-first principles, and building incrementally. These skills combine to make you effective in both interviews and real-world software development. Practice them deliberately, and they'll become second nature.
Module Summary:
This module covered the complete journey from design to code:
With these skills, you can confidently approach any LLD problem—whether in an interview or a production codebase—and systematically transform designs into high-quality, working code.