Bird
Raised Fist0

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

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
DRY, KISS, YAGNI - Principles That Prevent Over-Engineering in Interviews
Applying core OOP principles to design simple, maintainable, and extensible systems without over-engineering.
mediumOOP Foundations for InterviewsFreq: medium
Prerequisites:OOP FundamentalsJava concurrency basicsSOLID principles
⚙️
Functional Requirements
  • Eliminate duplicate code blocks by abstraction
  • Use minimal classes and interfaces necessary
  • Avoid adding features not currently required
  • Ensure thread-safe method execution using Java concurrency primitives
  • Allow extension via inheritance or composition without modifying existing code
  • Provide clear, maintainable Java code with proper method signatures
  • Use Java-specific constructs to enforce design decisions
👤
Use Cases
  • Design a system applying DRY to eliminate code duplication
  • Apply KISS to keep design simple and understandable
  • Avoid YAGNI by not adding unneeded features
  • Demonstrate thread-safe access to shared resources
  • Extend system without modifying existing classes
🧑‍💻
Actors
  • Developer
  • Code Reviewer
  • System Maintainer
❓
Clarifying Questions to Ask
  • How many concurrent threads will access shared resources?
  • Are there any performance constraints requiring lock-free designs?
  • What is the expected frequency of code changes or feature additions?
  • Should the system support dynamic configuration or runtime extension?
  • Are there any pricing or cost considerations affecting design?
  • What edge cases should be considered for concurrency and extensibility?
Out of Scope
✗ UI design
✗ Database persistence
✗ Network communication
✗ Advanced design patterns beyond basic SOLID
⚡
QPS Calculation
write qps100
read qps1000
🖥️
Server Estimate
2
🔢
Enumerations
enum DesignPrinciple
DRY | KISS | YAGNI
Enum prevents typo bugs and enables exhaustive switch statements when applying principles, ensuring only valid principles are referenced.
enum ComplexityLevel
SIMPLE | MODERATE | COMPLEX
Represents design complexity to enforce KISS by flagging overly complex components.
📦
CodeModule
3 fields
private final String moduleNameprivate final DesignPrinciple appliedPrincipleprivate volatile boolean isImplemented
Represents a unit of code designed with a specific principle applied; tracks implementation status with thread-safe visibility.
Why thisUsing a class with volatile and synchronized methods ensures thread-safe state management in Java.
Why notAvoid using external frameworks like Spring for this core design to keep it simple and focused.
public String getModuleName()Returns the name of the code module.
public boolean isImplemented()Returns the implementation status with volatile visibility guarantees.
public synchronized void markImplemented()Atomically marks the module as implemented to prevent race conditions.
📦
DesignReview
3 fields
private final CodeModule moduleprivate final String reviewerNameprivate final String comments
Captures review feedback on a CodeModule to ensure adherence to DRY, KISS, and YAGNI principles.
Why thisSimple POJO class to hold review data without complex dependencies.
Why notAvoid using database entities here as persistence is out of scope.
public String getReviewerName()Returns the name of the reviewer.
public String getComments()Returns the review comments.
📦
CodeBase
1 fields
private final List<CodeModule> modules
Aggregates multiple CodeModules and manages their lifecycle; enforces DRY by preventing duplicate modules.
Why thisSynchronized methods ensure thread-safe addition of modules.
Why notAvoid concurrent collections to keep design simple and explicit.
public synchronized boolean addModule(CodeModule module)Adds a new CodeModule if not already present; synchronized to prevent concurrent duplicates.
public List<CodeModule> getModules()Returns an unmodifiable list of modules.
📦
ExtensionPoint
2 fields
private final String extensionNameprivate final ComplexityLevel complexity
Defines a point where the system can be extended without modifying existing classes, enforcing Open/Closed principle.
Why thisUsing enums for complexity helps enforce KISS by limiting complexity.
Why notAvoid dynamic scripting or plugins to keep design straightforward.
public String getExtensionName()Returns the name of the extension point.
public ComplexityLevel getComplexity()Returns the complexity level to ensure KISS compliance.
📦
PrincipleEnforcer
1 fields
private final DesignPrinciple principle
Abstract class that enforces a design principle; holds state and behavior common to all enforcers, enabling extension without modification.
Why thisAbstract class allows polymorphic enforcement strategies.
Why notAvoid interfaces alone to share common code and state.
public abstract void enforce(CodeModule module)Abstract method to enforce the principle on a given CodeModule.
public DesignPrinciple getPrinciple()Returns the principle this enforcer applies.
📦
DryEnforcer
0 fields
Concrete subclass of PrincipleEnforcer that applies DRY checks and refactoring suggestions.
Why thisSynchronized enforce method prevents concurrent state corruption.
Why notAvoid complex concurrency utilities to keep design simple.
public synchronized void enforce(CodeModule module)Checks for duplicate code and suggests abstraction; synchronized to prevent concurrent modification of shared state.
📦
KissEnforcer
0 fields
Concrete subclass of PrincipleEnforcer that ensures simplicity by flagging complex modules.
Why thisSimple method without synchronization as no shared state is modified.
Why notAvoid complex heuristics to keep enforcement straightforward.
public void enforce(CodeModule module)Analyzes module complexity and flags violations of KISS.
📦
YagniEnforcer
0 fields
Concrete subclass of PrincipleEnforcer that prevents addition of unnecessary features.
Why thisSimple enforcement logic without concurrency concerns.
Why notAvoid speculative feature flags to prevent over-engineering.
public void enforce(CodeModule module)Checks if features are currently needed; warns if YAGNI violated.
🔗
Relationships
CodeBase◆──►CodeModule[1..*]
compositionCodeModules cannot exist independently of CodeBase; destroyed with CodeBase
DesignReview──►CodeModule[1]
associationDesignReview references CodeModule but does not own lifecycle
DryEnforcer──►PrincipleEnforcer[1]
inheritanceDryEnforcer is a subtype of PrincipleEnforcer
KissEnforcer──►PrincipleEnforcer[1]
inheritanceKissEnforcer is a subtype of PrincipleEnforcer
YagniEnforcer──►PrincipleEnforcer[1]
inheritanceYagniEnforcer is a subtype of PrincipleEnforcer
🗄️
CodeModule
PK: moduleName
🗄️
DesignReview
PK: moduleName
🔌
AddCodeModule
Request
{
  "moduleName": "String",
  "appliedPrinciple": "DesignPrinciple"
}
Response
{
  "success": "boolean",
  "message": "String"
}
Adds a new code module if not duplicate.
🔌
GetCodeModules
Request
{}
Response
{
  "modules": "List<CodeModule>"
}
Retrieves all code modules.
🔌
ReviewCodeModule
Request
{
  "moduleName": "String",
  "reviewerName": "String",
  "comments": "String"
}
Response
{
  "success": "boolean",
  "message": "String"
}
Adds a design review comment for a module.

Practice

(1/5)
1. In a vehicle rental system, should Vehicle be designed as an abstract class or an interface if it needs to hold common state like licensePlate and rentalStatus? Why?
easy
A. Abstract class, because it forces all subclasses to implement all methods explicitly.
B. Interface, because it allows multiple inheritance and enforces method signatures without state.
C. Interface, because Java interfaces can now have default methods and fields for state.
D. Abstract class, because it can hold shared state and provide default behavior for subclasses.

Solution

  1. Step 1: Analyze state requirements

    Vehicle needs to hold common mutable state like licensePlate and rentalStatus, which interfaces cannot hold as fields.
  2. Step 2: Choose correct OOP construct

    Abstract classes can have instance variables and default method implementations, making them suitable for shared state. Interfaces cannot hold instance state, only constants.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Shared mutable state -> abstract class, not interface [check]
Hint: Shared state needs abstract class, interfaces only define behavior [check]
Common Mistakes:
2. In a multi-threaded Chess game server, without synchronizing the makeMove() method, which exact sequence of thread interleaving causes two players to move simultaneously, violating turn order?
medium
A. Thread 1 reads current player as White, moves piece; Thread 2 reads current player as Black, moves piece simultaneously.
B. Thread 1 checks if it's White's turn -> true, context switch; Thread 2 checks if it's White's turn -> true; both proceed to move.
C. Thread 1 locks board, moves piece; Thread 2 waits but proceeds after unlock.
D. Thread 1 updates board state; Thread 2 reads stale board state but waits for turn.

Solution

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

    Both threads read the turn state before either updates it, causing both to believe it's their turn.
  2. Step 2: Trace thread interleaving

    Thread 1 checks turn -> true, context switch before move; Thread 2 checks turn -> true, both move simultaneously, violating turn order.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap allows double move without synchronization [check]
Hint: Check turn then act without lock -> race condition [check]
Common Mistakes:
3. In the playback queue, the method addTrack() is not synchronized. Two threads concurrently check if the queue has space and then add tracks. Which exact sequence causes a race condition leading to queue overflow?
medium
A. Thread 1 checks queue size -> space available, context switch, Thread 2 checks queue size -> space available, both add tracks exceeding capacity.
B. Thread 1 adds a track, Thread 2 waits until Thread 1 finishes, then adds another track safely.
C. Thread 1 locks queue, Thread 2 spins waiting for lock, no race occurs.
D. Thread 1 removes a track while Thread 2 adds, causing inconsistent queue state.

Solution

  1. Step 1: Identify the check-then-act pattern in addTrack()

    Both threads check queue size before adding, but without synchronization, the state can change between check and add.
  2. Step 2: Trace the race condition sequence

    Thread 1 checks size -> space available, context switch before adding. Thread 2 checks size -> space available, both add tracks, causing overflow.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized check-then-act -> double add -> overflow [check]
Hint: Check-then-act without sync -> race condition [check]
Common Mistakes:
4. To add a WeekendFeeStrategy for weekend parking fees without modifying existing classes, which SOLID principle is being applied and what is the minimal required change?
medium
A. Single Responsibility Principle; modify ParkingLot to add weekend fee logic.
B. Open-Closed Principle; create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing code.
C. Dependency Inversion Principle; invert dependencies by making ParkingLot depend on concrete weekend fee classes.
D. Liskov Substitution Principle; override existing fee methods in ParkingLot subclass for weekend fees.

Solution

  1. Step 1: Identify SOLID principle

    Open-Closed Principle (OCP) requires extending behavior without modifying existing classes.
  2. Step 2: Apply minimal change

    Create new WeekendFeeStrategy implementing FeeStrategy and inject it; no existing class changes needed.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    OCP: new fee class added, existing classes unchanged [check]
Hint: OCP: extend with new class, no existing code changes [check]
Common Mistakes:
5. The notification system must support ElectricCar vehicles that require ChargingSpot notifications. What is the minimum design change to support this without modifying existing classes?
hard
A. Modify Vehicle class to add isElectric flag and add charging logic in existing Spot classes.
B. Create a new ElectricCar class and ChargingSpot class, then modify NotificationFactory to handle them.
C. Add ELECTRIC_CAR to VehicleType enum and CHARGING to SpotType enum; create ElectricCar subclass and update SpotCompatibility map.
D. Add charging logic inside existing NotificationChannel implementations with conditional checks for ElectricCar.

Solution

  1. Step 1: Identify extensibility requirements

    New vehicle and spot types must be added without modifying existing classes to respect OCP.
  2. Step 2: Apply minimal changes

    Adding new enum values and creating new subclasses allows extension without modifying existing class code; updating compatibility map centralizes new logic.
  3. Step 3: Eliminate other options

    Options B, C, and D modify existing classes or factory logic, violating OCP and risking class explosion or god-class anti-pattern.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    New enums and subclasses extend design without modifying existing classes [check]
Hint: Add enums + subclasses, avoid modifying existing classes [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