Back to SOLID Principles
Q5Question 5 of 5

Which SOLID principles matter most in LLD?

Key Concepts
  • In day-to-day LLD, SRP, OCP, LSP, and DIP carry the most weight; ISP supports them.
  • SRP keeps each class focused on one reason to change, preventing merge conflicts and cross-concern bugs.
  • OCP lets you add new behaviour by adding a class, not editing tested code, reducing regression risk.
  • LSP ensures any implementation can drop into a service without special-case handling.
  • DIP makes services testable by depending on injected abstractions, not concrete classes.
  • SOLID is guidance, not dogma: applied mechanically it creates needless interfaces; applied with judgment it makes banking systems that evolve safely.

Definition

In LLD, SRP keeps classes focused, OCP adds behaviour without edits, LSP guarantees substitutability, and DIP makes everything testable through injected abstractions.
  • SOLID is a set of five object-oriented design principles that produce loosely coupled, highly cohesive, and easily extensible code.
  • SRP (Single Responsibility) – one class, one reason to change; prevents cross-concern bugs.
  • OCP (Open/Closed) – extend by adding a class, not editing tested code; reduces regression risk.
  • LSP (Liskov Substitution) – any subtype drops in without breaking callers; enables polymorphism.
  • DIP (Dependency Inversion) – depend on abstractions, inject implementations; makes code testable.
  • ISP (Interface Segregation) – keep interfaces small so classes are not forced to implement unused methods.

Think of National Bank of India as a well-run branch. Each employee does one job (SRP). To add a new service, you hire a new specialist rather than retraining everyone (OCP). Any trained teller can replace another without breaking the queue (LSP). And the manager gives instructions to a role like teller, not to a specific person named Rajesh, so anyone filling that role works (DIP).

Understanding the Concepts

Which SOLID principles matter most in LLD?

In real low-level design, four principles do most of the heavy lifting: SRP, OCP, LSP, and DIP. ISP supports them by keeping interfaces small.

How does SRP help in LLD?

Single Responsibility says a class should have one reason to change. A TransferService that also formats emails has two reasons to change; split the email part into NotificationService.

JAVA
package com.bank.service;

import java.math.BigDecimal;

// SRP applied: interest calculation is its own service
public class InterestService {
    public BigDecimal calculate(String accountType, BigDecimal balance) {
        if ("SAVINGS".equals(accountType)) {
            return balance.multiply(new BigDecimal("0.04"));
        }
        return balance.multiply(new BigDecimal("0.02"));
    }
}

// Notification is its own service
public class AlertService {
    public void sendInterestAlert(String customerId, BigDecimal interest) {
        System.out.println("Alert to " + customerId + ": Interest credited " + interest);
    }
}

A change to the SMS vendor never risks breaking interest math.

How does OCP help in LLD?

Open/Closed says code is open for extension but closed for modification. Adding a new account product means adding a strategy class, not editing a growing if-else.

JAVA
package com.bank.service;

import java.math.BigDecimal;

public interface InterestStrategy {
    BigDecimal calculate(BigDecimal balance);
}

public class SavingsInterest implements InterestStrategy {
    public BigDecimal calculate(BigDecimal balance) {
        return balance.multiply(new BigDecimal("0.04"));
    }
}

public class CurrentInterest implements InterestStrategy {
    public BigDecimal calculate(BigDecimal balance) {
        return balance.multiply(new BigDecimal("0.02"));
    }
}

// Adding NRE: new class, no edits to existing strategies
public class NreInterest implements InterestStrategy {
    public BigDecimal calculate(BigDecimal balance) {
        return balance.multiply(new BigDecimal("0.065"));
    }
}

The existing, regulator-approved logic is never re-tested and never re-risked.

How does LSP help in LLD?

Liskov Substitution says a subclass must honour the parent's contract so it can replace the parent without surprises.

JAVA
package com.bank.service;

import java.math.BigDecimal;

// LSP: any InterestStrategy drops in without special-case handling
public class InterestCalculator {
    private final InterestStrategy strategy;

    public InterestCalculator(InterestStrategy strategy) {
        this.strategy = strategy;
    }

    public BigDecimal calculate(BigDecimal balance) {
        // Works for SavingsInterest, CurrentInterest, NreInterest – any
        return strategy.calculate(balance);
    }
}

If Account.debit never throws for valid input, a SavingsAccount must not suddenly throw for the same input.

How does DIP help in LLD?

Dependency Inversion says high-level modules depend on abstractions, not concrete classes. Rajesh achieves this with constructor injection so he can swap implementations and mock in tests.

JAVA
package com.bank.service;

import java.math.BigDecimal;

public interface CreditScoreProvider {
    int fetchScore(String customerId);
}

public class LoanService {
    private final CreditScoreProvider provider;

    public LoanService(CreditScoreProvider provider) {
        this.provider = provider;
    }

    public boolean approve(String customerId, BigDecimal amount) {
        return provider.fetchScore(customerId) > 700;
    }
}

// In tests: inject a mock
// LoanService svc = new LoanService(id -> 800);
// assertTrue(svc.approve("CUST-001", new BigDecimal("500000")));

How does ISP support the other principles?

Interface Segregation keeps interfaces small so classes are not forced to implement unused methods. Small contracts are easier to honour fully (supporting LSP) and easier to mock (supporting DIP).

JAVA
package com.bank.model;

import java.math.BigDecimal;

// Small, focused interfaces
public interface InterestBearing {
    BigDecimal calculateInterest(BigDecimal balance);
}

public interface Overdraftable {
    BigDecimal overdraftLimit();
}

public interface Tradable {
    void tradeStock(String symbol, int qty);
}

// Savings implements only what it needs
public class SavingsAccount implements InterestBearing {
    public BigDecimal calculateInterest(BigDecimal balance) {
        return balance.multiply(new BigDecimal("0.04"));
    }
}

// Trading account implements trading + interest
public class TradingAccount implements InterestBearing, Tradable {
    public BigDecimal calculateInterest(BigDecimal balance) {
        return balance.multiply(new BigDecimal("0.02"));
    }
    public void tradeStock(String symbol, int qty) {
        System.out.println("Trade " + qty + " of " + symbol);
    }
}

How do all five work together in a banking module?

Pankaj, the architect at National Bank of India, designed the interest module using all five:

JAVA
package com.bank.demo;

import java.math.BigDecimal;
import java.util.Map;

public class SolidInAction {
    public static void main(String[] args) {
        // OCP + DIP: strategies selected at runtime via map
        Map<String, InterestStrategy> strategies = Map.of(
            "SAVINGS", new SavingsInterest(),
            "CURRENT", new CurrentInterest(),
            "NRE", new NreInterest()
        );

        // LSP: any strategy drops in
        for (var entry : strategies.entrySet()) {
            BigDecimal interest = entry.getValue().calculate(new BigDecimal("1000000"));
            System.out.println(entry.getKey() + " interest on 10L: " + interest);
        }
    }
}
TEXT
SAVINGS interest on 10L: 40000.00
CURRENT interest on 10L: 20000.00
NRE interest on 10L: 65000.000

When should you not apply SOLID mechanically?

SOLID is guidance, not religion. Applied mechanically it creates needless interfaces and one-line classes.

JAVA
// OVER-APPLIED: interface for a class that will never have a second implementation
public interface FlatFeeCalculator {
    BigDecimal calculate(BigDecimal amount);
}
public class FlatFeeCalculatorImpl implements FlatFeeCalculator {
    public BigDecimal calculate(BigDecimal amount) {
        return new BigDecimal("50.00");
    }
}

// BETTER: plain method until a second implementation actually appears
public class FeeService {
    public BigDecimal flatFee() {
        return new BigDecimal("50.00");
    }
}

What should a senior engineer know?

  • SOLID principles are tools to manage change and coupling where they genuinely exist.
  • In Spring, DIP is automatic through constructor injection, and OCP is natural with bean registries.
  • In a microservice, SRP maps to bounded contexts: each service has one business reason to exist.
  • The real interview signal is knowing when to apply and when to hold back.

Visual Overview

DIAGRAM
  SOLID in an LLD Banking Module
  ──────────────────────────────

  ┌─────────────────┐
  │  InterestModule  │  (SRP: only calculates interest)
  │  ───────────     │
  │  - strategy map  │
  └────────┬────────┘
           │ DIP: depends on interface
           ▼
  ┌──────────────────────┐
  │  InterestStrategy    │ ◄── interface (ISP: one method)
  └──────────┬───────────┘
             │ LSP: any impl drops in
     ┌───────┼───────┬───────────┐
     ▼       ▼       ▼           ▼
  Savings  Current   NRE      FixedDeposit
  (0.04)   (0.02)   (0.065)   (new – OCP)

  Adding FixedDeposit = new class
  InterestModule + existing strategies: UNTOUCHED

Key Takeaway

SOLID is five principles, and in real low-level design four of them do most of the heavy lifting. Single Responsibility means each class has exactly one reason to change, so Rajesh keeps interest calculation in an interest service and notifications in an alert service at National Bank of India, because a change to the SMS vendor should never risk breaking interest math.

Open/Closed means code is open for extension but closed for modification. When the bank launches a new deposit product, Pankaj adds a new strategy class implementing the interest interface rather than editing a growing if-else, so he never re-tests the existing, regulator-approved logic.

Liskov Substitution means any implementation must honour the contract, so wherever the service expects an interest strategy, any concrete strategy drops in and behaves correctly without special cases. Dependency Inversion means the high-level service depends on the strategy abstraction, not on a concrete class, which Rajesh wires through constructor injection, making the service trivial to unit test with a mock.

ISP keeps interfaces small so classes implement only what they need. The senior insight is that SOLID is judgment, not dogma: applied mechanically it breeds needless interfaces, but applied thoughtfully it lets the interest module absorb new products and regulatory changes without destabilising proven code.

Remember one sentence: in LLD, SRP focuses each class, OCP extends by adding, LSP guarantees substitutability, and DIP makes everything testable through injected abstractions.

---

Share
Use ← → arrow keys to navigate