ISP and codebase flexibility — smaller interfaces, fewer forced changes
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
ISP improves flexibility by ensuring that a class depends only on the capability it uses. If invoice generation changes, payment implementations that never generate invoices should not be pulled into that change simply because both operations share one large interface.
For example, a card processor can implement PaymentProcessor, while only invoice-capable services implement InvoiceGenerator. An invoice controller depends on the second contract, and ordinary payment clients never see it. Tests also fake only the relevant methods.
This localizes changes rather than eliminating them. Implementations of a changed interface still need review, and badly fragmented interfaces add complexity. The useful boundary follows distinct consumers, supported capabilities, and change patterns.
ISP and codebase flexibility — smaller interfaces, fewer forced changes
Direct Answer
ISP improves flexibility by reducing accidental dependencies. When clients depend on focused capabilities, a change to invoice generation does not affect payment processors that never generate invoices. Implementations, tests, and consumers can change only where the relevant contract is used, provided the interfaces reflect real boundaries.
Code Example
// Without ISP: fat interface used by many classes
interface PaymentProcessor {
void processPayment(Payment payment);
void refundPayment(Long paymentId);
void generateInvoice(Long paymentId); // New requirement added
}
// All 5 implementing classes must now implement generateInvoice
class CreditCardProcessor implements PaymentProcessor { /* must add generateInvoice */ }
class PayPalProcessor implements PaymentProcessor { /* must add generateInvoice */ }
class GiftCardProcessor implements PaymentProcessor { /* must add generateInvoice */ }
class CryptoProcessor implements PaymentProcessor { /* must add generateInvoice */ }
class BankTransferProcessor implements PaymentProcessor { /* must add generateInvoice */ }
// With ISP: focused interfaces
interface PaymentProcessor { void processPayment(Payment payment); }
interface RefundablePayment { void refundPayment(Long paymentId); }
interface InvoiceGenerator { void generateInvoice(Long paymentId); } // New interface
// Only classes that need invoices implement InvoiceGenerator
class CreditCardProcessor implements PaymentProcessor, RefundablePayment, InvoiceGenerator {
public void processPayment(Payment p) { /* logic */ }
public void refundPayment(Long id) { /* logic */ }
public void generateInvoice(Long id) { /* new logic */ }
}
// GiftCardProcessor is unaffected by the invoice feature
class GiftCardProcessor implements PaymentProcessor {
public void processPayment(Payment p) { /* logic */ }
}
Common Mistakes & Pitfalls
- Promising that ISP removes all ripple effects: implementations and clients of a changed capability still need review.
- Using implementer count as the only metric: examine which consumers use which operations and how the contract evolves.
- Testing meaningless stub methods: redesign the capability instead of normalizing invalid implementations.
- Over-fragmenting: too many tiny interfaces can make construction and navigation harder.
Check your understanding
Answer these without looking back at the article.
- How does ISP reduce compilation dependencies?
- How does ISP relate to the Dependency Inversion Principle?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.