Bird
Raised Fist0

DRY, KISS, YAGNI - Principles That Prevent Over-Engineering in Interviews

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Parking Lot System with DRY, KISS, YAGNI Principles
40 min ยท medium
medium

Design a parking lot system with 5 levels and 4 gates per level. The system must handle parking spot allocation, vehicle entry/exit, and pricing based on vehicle type and parking duration. It should avoid over-engineering by applying DRY, KISS, and YAGNI principles.

  • There are 5 levels, each with 4 gates for entry and exit.
  • Vehicles are categorized by type (e.g., Car, Motorcycle, Truck).
  • Pricing depends on vehicle type and parking duration.
  • Parking spots are limited and must be allocated atomically to avoid conflicts.
  • The system should be extensible but avoid unnecessary abstractions or features.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Vehicle Types Using Enum
Design a way to represent vehicle types that ensures type safety and prevents runtime errors.
๐Ÿ’ก Hint
Use Java enum to represent fixed set of vehicle types, enabling exhaustive switch statements and IDE support.
Use enum VehicleType {CAR, MOTORCYCLE, TRUCK} - enums provide compile-time safety and prevent typo bugs unlike Strings.
Mid: Recognizes enum usage for fixed types. Senior: Explains how enum enables exhaustive switch and prevents runtime errors due to typos.
โš ๏ธCommon Wrong Answer
Using String constants for vehicle types leads to runtime bugs from typos and no compile-time checks.
Step 2
Model Vehicle as Abstract Class with State
Design the Vehicle abstraction considering shared state and behavior.
๐Ÿ’ก Hint
Use abstract class to hold common fields like licensePlate and vehicleType; interfaces cannot hold instance state in Java.
abstract class Vehicle with fields licensePlate (String) and vehicleType (VehicleType) - abstract class needed because interfaces cannot hold instance state in Java.
Mid: Uses abstract class for shared state. Senior: Explains why interface is insufficient due to lack of fields and default state management.
โš ๏ธCommon Wrong Answer
Using interface for Vehicle with fields leads to compilation errors since interfaces cannot hold instance state.
Step 3
Implement Thread-Safe Parking Spot Allocation
Design a method to allocate parking spots atomically to avoid race conditions when multiple threads park vehicles concurrently.
๐Ÿ’ก Hint
Use synchronized method parkVehicle(Vehicle v) to atomically check and set spot availability, preventing double allocation.
synchronized boolean parkVehicle(Vehicle v) - synchronized ensures atomic check-and-set of spot availability, preventing race conditions.
Mid: Uses synchronized to protect critical section. Senior: Traces thread interleaving where without synchronized, two threads read AVAILABLE and both allocate same spot.
โš ๏ธCommon Wrong Answer
Not synchronizing parkVehicle leads to race conditions where multiple threads allocate the same spot concurrently.
Step 4
Design Pricing Strategy with Minimal Abstraction
Implement pricing logic that calculates fees based on vehicle type and duration without over-engineering.
๐Ÿ’ก Hint
Use simple enum-based switch pricing inside a PricingService class; avoid unnecessary strategy pattern unless multiple pricing algorithms are required.
PricingService with method calculateFee(VehicleType type, Duration duration) using switch on enum - avoids over-engineering by not introducing unnecessary interfaces or patterns.
Mid: Implements pricing with switch-case on enum. Senior: Explains YAGNI by avoiding Strategy pattern since only one pricing logic exists currently.
โš ๏ธCommon Wrong Answer
Introducing Strategy pattern prematurely adds complexity and violates YAGNI since only one pricing algorithm is needed.
Step 5
Manage Levels and Gates with Composition
Model the relationship between ParkingLot, Levels, and Gates considering lifecycle ownership.
๐Ÿ’ก Hint
Use composition: ParkingLot owns Levels, Levels own Gates; Levels and Gates cannot exist independently outside ParkingLot.
Composition: ParkingLot contains List<Level>, Level contains List<Gate> - strong lifecycle ownership ensures Levels and Gates do not exist independently.
Mid: Correctly models composition relationships. Senior: Explains lifecycle ownership and why aggregation or association is inappropriate here.
โš ๏ธCommon Wrong Answer
Using aggregation or association for Levels and Gates leads to unclear ownership and potential lifecycle bugs.
๐Ÿ”Œ
API Design Challenge
Design APIs for vehicle entry, exit, and fee calculation that are simple, thread-safe, and easy to maintain.
  • Use synchronized methods or locks to ensure thread safety during spot allocation.
  • Keep API methods focused and minimal to adhere to KISS principle.
  • Avoid adding unnecessary parameters or complex abstractions that are not currently needed.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Use enum for VehicleType to ensure type safety
  • abstract class Vehicle with shared instance state fields
  • synchronized method for atomic parking spot allocation
  • Composition relationships with clear lifecycle ownership
  • Pricing logic implemented with enum switch avoiding over-engineering
Good to Have - 30%
  • Explanation of why interface is insufficient for Vehicle state
  • Avoidance of premature Strategy pattern (YAGNI)
  • Use of volatile if singleton pattern applied
  • Clear Java method signatures with concurrency keywords
  • DRY principle applied in Vehicle and PricingService reuse
Bonus - 10%
  • Detailed thread interleaving explanation for race condition
  • Justification of design decisions referencing Java language specifics
  • Identification of anti-patterns with concrete runtime consequences
  • Use of Java 8+ features like Duration for time representation
  • Clear separation of concerns in class responsibilities
Using String for VehicleType or SpotType leads to typo bugs and runtime errors; enum prevents this at compile time.
Not synchronizing parking spot allocation causes race conditions and double booking.
Premature use of Strategy pattern violates YAGNI and adds unnecessary complexity.
Using interface for Vehicle state causes compilation errors since interfaces cannot hold instance fields.
Using aggregation or association instead of composition for Levels and Gates leads to unclear lifecycle ownership and potential bugs.
โœ…
Model Answer
โ–ผ Reveal

The design uses Java enums to represent fixed vehicle types, ensuring compile-time safety and preventing runtime errors. Vehicle is modeled as an abstract class holding shared instance state (licensePlate, vehicleType) because interfaces cannot hold fields in Java.

  • Use of Java enum for VehicleType to ensure compile-time safety and prevent typo bugs.
  • Model Vehicle as abstract class to hold shared state since interfaces cannot have instance fields.
  • Synchronize parkVehicle method to atomically allocate parking spots and prevent race conditions.
  • Implement pricing logic with simple switch on VehicleType enum inside PricingService to avoid premature Strategy pattern (YAGNI).
  • Use composition for ParkingLot, Levels, and Gates to enforce lifecycle ownership and avoid bugs from aggregation or association.
  • Use enum VehicleType to represent fixed vehicle categories, ensuring compile-time safety.
  • abstract class Vehicle holds shared instance state (licensePlate, vehicleType) - interfaces cannot hold fields in Java.
  • synchronized boolean parkVehicle(Vehicle v) method to atomically allocate parking spots, preventing race conditions.
  • PricingService uses switch on VehicleType enum for fee calculation, avoiding premature Strategy pattern (YAGNI).
  • Composition relationship: ParkingLot owns Levels, Levels own Gates, enforcing lifecycle ownership.
  • Avoid unnecessary abstractions and patterns to keep design simple and maintainable (KISS).
  • Apply DRY by reusing Vehicle base class and centralized PricingService.
  • Use volatile for singleton instance if ParkingLot is implemented as singleton (optional extension).
  • Avoid String constants for types to prevent runtime bugs and poor IDE support.
  • Design methods with clear Java signatures including synchronized and volatile where needed.

Practice

(1/5)
1. In designing a collection class that supports iteration, should the iterator interface be an abstract class or an interface in Java, and why?
easy
A. Interface, because it enforces immutability of the collection during iteration.
B. Abstract class, because it allows sharing state and default implementation for iteration methods.
C. Abstract class, because Java interfaces cannot have methods with parameters.
D. Interface, because it defines behavior without state, allowing multiple implementations and better flexibility.

Solution

  1. Step 1: Understand Java interface vs abstract class

    Interfaces define behavior contracts without state, allowing multiple implementations. Abstract classes can hold state but restrict multiple inheritance.
  2. Step 2: Apply to iterator design

    Iterator interface defines traversal methods (e.g., hasNext(), next()) without state, enabling different iterator implementations per collection type.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Iterator as interface enables flexible, stateless traversal contracts [check]
Hint: Iterator interface defines behavior, no state needed [check]
Common Mistakes:
2. A class has a private constructor and a static synchronized getInstance() method that returns the single instance. Which pattern is applied and why is this the CORRECT use case?
easy
A. Singleton pattern, because it ensures only one instance with thread-safe lazy initialization.
B. Factory Method pattern, because it controls object creation through subclassing.
C. Builder pattern, because it separates construction from representation.
D. Prototype pattern, because it clones existing instances to avoid new creation.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance is created lazily and safely in multithreaded contexts.
  2. Step 2: Eliminate other creational patterns

    Factory Method uses subclassing, Builder separates construction steps, and Prototype clones objects; none match the private constructor plus static synchronized method pattern.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + synchronized static method -> Singleton [check]
Common Mistakes:
3. In the Jigsaw Puzzle System, the Board class has a placePiece() method that first checks if a position is free and then places the piece. Without synchronizing placePiece(), what exact sequence of thread interleaving causes two threads to place different pieces at the same position (double-booking)?
medium
A. Thread 1 checks position free -> true, context switch; Thread 2 checks position free -> true; both place pieces concurrently.
B. Thread 1 places piece, Thread 2 waits, then places piece after Thread 1 finishes.
C. Thread 1 and Thread 2 both call placePiece() but on different board instances, causing no conflict.
D. Thread 1 locks the board, Thread 2 spins waiting, so no double-booking occurs.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 checks position free and sees true, but before placing, context switches.
  2. Step 2: Thread 2 also checks position free -> true and proceeds to place piece concurrently, causing double-booking.

  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized check-then-act allows both threads to place pieces at same position [check]
Hint: Check-then-act without sync -> double-booking race [check]
Common Mistakes:
4. In a multi-threaded Java Observer implementation, if notifyObservers() is not synchronized, which exact sequence of events can cause observers to receive inconsistent or duplicate updates?
medium
A. Thread 1 checks observer list size -> 3, context switch; Thread 2 removes an observer -> size 2; Thread 1 continues notifying 3 observers, causing duplicate notifications.
B. Thread 1 calls notifyObservers(), Thread 2 waits until Thread 1 finishes, then calls notifyObservers().
C. Thread 1 and Thread 2 both add observers simultaneously, causing a ConcurrentModificationException during notification.
D. Thread 1 updates subject state, Thread 2 reads old state before notification, causing stale data delivery.

Solution

  1. Step 1: Identify race condition in notifyObservers()

    Without synchronization, Thread 1 reads observer list size as 3, then context switches.
  2. Step 2: Thread 2 modifies observer list by removing one observer, reducing size to 2.

    Thread 1 resumes and tries to notify 3 observers, causing out-of-date or duplicate notifications.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized notifyObservers() -> stale observer list read -> inconsistent notifications [check]
Hint: Unsynchronized notify reads stale observer list -> duplicate notifications [check]
Common Mistakes:
5. The Workflow Engine must support ElectricCar vehicles requiring ChargingSpot compatibility. What is the minimum change to the current design to add this without modifying existing classes?
hard
A. Add charging logic inside Executor class to handle ElectricCar spots dynamically.
B. Modify Vehicle base class to include isElectric flag and add charging logic inside existing Spot classes.
C. Create a new ElectricVehicle interface and modify all existing Vehicle subclasses to implement it.
D. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility.COMPATIBILITY_MAP accordingly.

Solution

  1. Step 1: Identify extensibility requirements

    Adding ElectricCar and ChargingSpot must not modify existing classes to respect OCP and avoid class explosion.
  2. Step 2: Use enums and subclassing

    Adding new enum values and a new ElectricCar subclass extends functionality cleanly; updating compatibility map centralizes spot-vehicle matching without changing existing code.
  3. Step 3: Avoid modifying base classes or Executor

    Modifying Vehicle or Executor violates OCP and risks introducing bugs.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    New enums + subclass + compatibility map update -> minimal, no existing class change [check]
Hint: Add enums + subclass + map update, no existing class change [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard