Skip to content

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 is a collection of five object-oriented design principles that improve code maintainability and extensibility.

A class should have only one reason to change.

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.

class SalaryCalculator {
void calculateSalary() {
}
}
class EmployeeRepository {
void save(Employee employee) {
}
}

Each class now has a single responsibility.


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

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.


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() {
}
}

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

Avoid duplicate logic.

calculateTaxForEmployee();
calculateTaxForCustomer();
calculateTax();

Benefits:

  • Less maintenance
  • Fewer bugs
  • Better reuse

Prefer the simplest solution.

Instead of writing a complex algorithm unnecessarily, use the simplest implementation that satisfies the requirements.


Do not build functionality until it is actually required.

Bad example:

Adding support for ten payment providers when only one is currently needed.


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

Bad

int d;

Good

int employeeAge;

Instead of

processEmployee();

containing 500 lines,

split into

validate();
calculateSalary();
saveEmployee();
sendNotification();

Bad

if(age > 18)

Good

private static final int ADULT_AGE = 18;
if(age > ADULT_AGE)

One method should perform one logical operation.


Bad

class Car extends Engine

Good

class Car {
Engine engine;
}

Composition provides greater flexibility and reduces coupling.


Leave the code cleaner than you found it.

Examples

  • Remove dead code
  • Improve naming
  • Refactor duplication
  • Improve readability

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.


  • 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.