SOLID, DRY, KISS, and Clean Code Principles
Design principles help developers build software that is maintainable, scalable, testable, extensible, and easy to understand. This guide covers the code-level principles expected in Java Full Stack interviews.
SOLID Principles
Section titled “SOLID Principles”SOLID is a collection of five object-oriented design principles that improve code maintainability and extensibility.
Single Responsibility Principle (SRP)
Section titled “Single Responsibility Principle (SRP)”A class should have only one reason to change.
Bad Example
Section titled “Bad Example”class Employee {
void calculateSalary() { }
void saveToDatabase() { }}The Employee class has two responsibilities:
- Salary calculation
- Database persistence
A change in either responsibility forces the class to change.
Good Example
Section titled “Good Example”class SalaryCalculator {
void calculateSalary() { }}
class EmployeeRepository {
void save(Employee employee) { }}Each class now has a single responsibility.
Open Closed Principle (OCP)
Section titled “Open Closed Principle (OCP)”Software entities should be open for extension but closed for modification.
interface Payment {
void pay();}
class CreditCardPayment implements Payment {
@Override public void pay() { System.out.println("Credit Card Payment"); }}
class UpiPayment implements Payment {
@Override public void pay() { System.out.println("UPI Payment"); }}Instead of modifying existing code, simply add a new implementation.
Benefits:
- Less regression
- Easier testing
- Easier maintenance
Liskov Substitution Principle (LSP)
Section titled “Liskov Substitution Principle (LSP)”A subclass should be able to replace its parent class without breaking the application.
class Bird {
void eat() { }}
class Sparrow extends Bird {}
Bird bird = new Sparrow();Every subclass should preserve the behavior expected from the parent.
Interface Segregation Principle (ISP)
Section titled “Interface Segregation Principle (ISP)”Clients should not be forced to depend on methods they don’t use.
interface Animal {
void fly();
void swim();}A fish cannot fly.
interface Flyable {
void fly();}
interface Swimmable {
void swim();}
class Fish implements Swimmable {
@Override public void swim() { }}
class Bird implements Flyable {
@Override public void fly() { }}Dependency Inversion Principle (DIP)
Section titled “Dependency Inversion Principle (DIP)”Depend upon abstractions, not concrete implementations.
interface PaymentGateway {
void pay();}
class StripeGateway implements PaymentGateway {
@Override public void pay() { }}
class OrderService {
private PaymentGateway paymentGateway;
public OrderService(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }}Benefits:
- Easier unit testing
- Loose coupling
- Better extensibility
DRY (Don’t Repeat Yourself)
Section titled “DRY (Don’t Repeat Yourself)”Avoid duplicate logic.
calculateTaxForEmployee();calculateTaxForCustomer();calculateTax();Benefits:
- Less maintenance
- Fewer bugs
- Better reuse
KISS (Keep It Simple, Stupid)
Section titled “KISS (Keep It Simple, Stupid)”Prefer the simplest solution.
Instead of writing a complex algorithm unnecessarily, use the simplest implementation that satisfies the requirements.
YAGNI (You Aren’t Gonna Need It)
Section titled “YAGNI (You Aren’t Gonna Need It)”Do not build functionality until it is actually required.
Bad example:
Adding support for ten payment providers when only one is currently needed.
Separation of Concerns (SoC)
Section titled “Separation of Concerns (SoC)”Each layer should have a dedicated responsibility.
flowchart TD Client --> Controller Controller --> Service Service --> Repository Repository --> Database
Typical layered architecture:
- Controller → Request handling
- Service → Business logic
- Repository → Database interaction
- Database → Data storage
Clean Code Practices
Section titled “Clean Code Practices”Meaningful Names
Section titled “Meaningful Names”Bad
int d;Good
int employeeAge;Small Methods
Section titled “Small Methods”Instead of
processEmployee();containing 500 lines,
split into
validate();
calculateSalary();
saveEmployee();
sendNotification();Avoid Magic Numbers
Section titled “Avoid Magic Numbers”Bad
if(age > 18)Good
private static final int ADULT_AGE = 18;
if(age > ADULT_AGE)Single Level of Abstraction
Section titled “Single Level of Abstraction”One method should perform one logical operation.
Prefer Composition over Inheritance
Section titled “Prefer Composition over Inheritance”Bad
class Car extends EngineGood
class Car {
Engine engine;}Composition provides greater flexibility and reduces coupling.
Boy Scout Rule
Section titled “Boy Scout Rule”Leave the code cleaner than you found it.
Examples
- Remove dead code
- Improve naming
- Refactor duplication
- Improve readability
Interview Answer (9+ Years Experience)
Section titled “Interview Answer (9+ Years Experience)”If asked:
What design principles do you follow while developing enterprise applications?
A strong answer covering all areas — code, system, API, database, and UI/UX design:
I follow SOLID principles to create maintainable and extensible object-oriented code. I apply DRY, KISS, YAGNI, and Separation of Concerns to reduce duplication and keep the codebase simple and modular. For system design, I focus on scalability, availability, reliability, fault tolerance, loose coupling, and high cohesion. I design RESTful APIs that are stateless, versioned, and idempotent while following proper HTTP semantics. In database design, I apply normalization, indexing, appropriate key design, and use denormalization, replication, or sharding only when required for performance and scalability. For UI/UX, I prioritize consistency, accessibility, responsiveness, clear visual hierarchy, and user feedback to deliver intuitive user experiences. These principles help build scalable, maintainable, and production-ready enterprise applications.
Key Takeaways
Section titled “Key Takeaways”- Apply SOLID, DRY, KISS, YAGNI, and Separation of Concerns to write maintainable, testable, and extensible code.
- SRP and OCP reduce regression risk: single-responsibility classes change less often, and open-for-extension designs avoid modifying tested code.
- Prefer composition over inheritance, and keep methods small and at a single level of abstraction.
- The Boy Scout Rule — leaving code cleaner than you found it — compounds over time into a maintainable codebase.