Loading learning content...
Recognizing ISP violations is essential, but preventing them requires a systematic process. Experienced engineers don't rely solely on intuition—they use mental checklists and evaluation frameworks to catch design flaws before code is written.
This page provides a comprehensive ISP checklist that you can apply during interface design, code reviews, and refactoring efforts. In addition to identifying problems, this checklist guides you toward ISP-compliant solutions. By internalizing this process, you'll move from reactive violation detection to proactive, principle-driven design.
By the end of this page, you will have a practical, step-by-step checklist for evaluating interfaces against ISP, understand when to apply each check, and be able to systematically assess both new interface proposals and existing interfaces being refactored.
The ISP checklist consists of five phases, each focusing on a different dimension of interface quality. Apply these checks in sequence when designing new interfaces, or use individual checks as diagnostic tools when reviewing existing code.
The Five Phases:
Use the complete checklist for: new interface designs, major refactoring efforts, API boundary definitions, and architecture reviews. For day-to-day code reviews, focus on the most relevant phase. For example, when reviewing a PR that adds methods to an existing interface, focus on Phase 3 (Method Grouping) and Phase 5 (Evolution Projection).
The first phase answers a fundamental question: Who will depend on this interface, and what does each consumer actually need?
Interfaces exist to serve clients. Starting design from the client perspective—rather than from the implementation—naturally leads to ISP-compliant designs.
123456789101112131415161718192021222324252627282930313233343536373839404142
// Proposed interfaceinterface OrderService { createOrder(cart: Cart): Promise<Order>; getOrder(id: string): Promise<Order>; getOrdersByCustomer(customerId: string): Promise<Order[]>; cancelOrder(id: string): Promise<void>; fulfillOrder(id: string): Promise<void>; refundOrder(id: string): Promise<void>; generateOrderReport(criteria: ReportCriteria): Promise<Report>;} // CLIENT ANALYSIS: // Consumer 1: CheckoutController// - Uses: createOrder// - Cluster: Order Creation // Consumer 2: OrderDetailsPage // - Uses: getOrder// - Cluster: Order Reading // Consumer 3: CustomerDashboard// - Uses: getOrder, getOrdersByCustomer, cancelOrder// - Cluster: Customer Order Access // Consumer 4: WarehouseSystem// - Uses: getOrder, fulfillOrder// - Cluster: Order Fulfillment // Consumer 5: CustomerServiceDashboard// - Uses: getOrder, cancelOrder, refundOrder// - Cluster: Order Modification // Consumer 6: AnalyticsService// - Uses: generateOrderReport// - Cluster: Order Reporting // ANALYSIS RESULT:// - No single consumer uses all 7 methods// - Clear clustering: Creation, Reading, Fulfillment, Modification, Reporting// - generateOrderReport used only by AnalyticsService - separate concern// - This interface should likely be split into 4-5 focused interfaces| Finding | Implication | Action |
|---|---|---|
| All consumers use all methods | Interface is appropriately sized | Keep unified |
| Clear consumer clusters with distinct method sets | Interface serves multiple roles | Split by role |
| One consumer uses method, others don't | Method may not belong in shared interface | Evaluate separate interface or extension |
| Method never used by any consumer | Dead code or over-anticipated feature | Remove or defer to when needed |
Phase 2 examines whether the interface represents a single role or conflates multiple roles. Each interface should represent one coherent role that a consumer might play.
A "role" in this context means a purposeful relationship a consumer has with the interface. For example, Reader, Writer, Observer, Publisher are roles. DataAccessor conflates reading and writing. EntityManager conflates CRUD, querying, transactions, and lifecycle management.
Reader, Creator, Validator. Bad: DataAccessorAndValidator, ReaderWriterObserverMessageSender. Bad: KafkaMessagePublisher (implementation leaked)UserService (does everything user-related)DataManager (manages all data operations)EntityHandler (handles entities how?)OrderFacade (facade for all order ops)PaymentProcessor (processes AND reports?)UserAuthenticator (authenticates users)UserProfileReader (reads user profiles)OrderCreator (creates orders)PaymentAuthorizer (authorizes payment)InventoryChecker (checks inventory)Uncle Bob's formulation of SRP uses "actor" (stakeholder who requests changes) to identify responsibilities. Apply the same thinking to roles: If the Sales team requests changes to some methods while the Compliance team requests changes to others, those are different roles needing different interfaces. Changes from different stakeholders shouldn't flow through the same interface.
Phase 3 analyzes the methods themselves: Which methods naturally belong together based on what they do, what data they access, and how they change?
Methods that are cohesive—operating on the same data, changing together, serving the same purpose—should be in the same interface. Methods that are incidentally grouped ("they're all about users") should be examined for separation.
methodA requires no knowledge of methodB, they might be separableread without write, start without stop might indicate incomplete interface or intentional asymmetry123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
// Analyzing method cohesioninterface DocumentService { // Group A: Document Lifecycle createDocument(content: string): Promise<Document>; openDocument(id: string): Promise<Document>; saveDocument(doc: Document): Promise<void>; closeDocument(id: string): Promise<void>; // Group B: Content Operations insertText(docId: string, position: number, text: string): Promise<void>; deleteText(docId: string, start: number, end: number): Promise<void>; formatText(docId: string, range: Range, format: Format): Promise<void>; // Group C: Collaboration shareDocument(docId: string, users: User[]): Promise<void>; revokeAccess(docId: string, userId: string): Promise<void>; getCollaborators(docId: string): Promise<User[]>; // Group D: Export exportToPDF(docId: string): Promise<Buffer>; exportToWord(docId: string): Promise<Buffer>; exportToMarkdown(docId: string): Promise<string>; // Group E: Version Control createVersion(docId: string, label: string): Promise<Version>; listVersions(docId: string): Promise<Version[]>; restoreVersion(docId: string, versionId: string): Promise<void>;} // GROUPING ANALYSIS:// // Group A (Lifecycle): Changed by storage team, cohesive// → DocumentLifecycle interface// // Group B (Content): Changed by editor team, cohesive // → ContentOperations interface//// Group C (Collaboration): Changed by social/sharing team// → DocumentCollaboration interface//// Group D (Export): Changed by export/compatibility team// → DocumentExporter interface//// Group E (Version Control): Changed by VCS team// → DocumentVersioning interface//// Each group:// 1. Changes together (same team/feature)// 2. Shares types (Document, docId patterns)// 3. Can be implemented independently// 4. Has clear single responsibility| Indicator | High Cohesion (Keep Together) | Low Cohesion (Consider Splitting) |
|---|---|---|
| Change frequency | Methods change in same commits/PRs | Methods change independently |
| Data access | Methods read/write same fields | Methods touch disjoint data |
| Call patterns | Methods called in sequence by consumers | Methods never called together |
| Implementation coupling | Implementing one requires understanding other | Methods implemented in isolation |
| Error handling | Methods share error types/recovery | Errors are method-specific |
Phase 4 quantifies the dependency burden the interface creates: How heavy is the cost of depending on this interface?
A well-designed interface minimizes the cognitive and compilation burden on consumers. A fat interface forces consumers to understand, import, and potentially mock methods they never use.
1234567891011121314151617181920212223242526272829303132333435363738394041424344
// Quantifying dependency burden interface PaymentGateway { // 15 methods - above red flag threshold processPayment(amount: Money, method: PaymentMethod): Promise<Transaction>; refundPayment(transactionId: string): Promise<void>; voidTransaction(transactionId: string): Promise<void>; getTransaction(transactionId: string): Promise<Transaction>; listTransactions(filter: TransactionFilter): Promise<Transaction[]>; createSubscription(plan: Plan, customer: Customer): Promise<Subscription>; cancelSubscription(subscriptionId: string): Promise<void>; pauseSubscription(subscriptionId: string): Promise<void>; resumeSubscription(subscriptionId: string): Promise<void>; updateSubscriptionPlan(subscriptionId: string, plan: Plan): Promise<void>; generateInvoice(customerId: string): Promise<Invoice>; downloadInvoice(invoiceId: string): Promise<Buffer>; sendInvoiceEmail(invoiceId: string): Promise<void>; getPaymentAnalytics(criteria: AnalyticsCriteria): Promise<Analytics>; exportTransactionsToCSV(filter: TransactionFilter): Promise<string>;} // DEPENDENCY ASSESSMENT: // Consumer: CheckoutService// Methods used: processPayment, getTransaction (2/15 = 13%)// Mock burden: Must mock 15 methods for tests// Verdict: OVER-BROAD ❌ // Consumer: SubscriptionManager// Methods used: createSubscription, cancelSubscription, pauseSubscription,// resumeSubscription, updateSubscriptionPlan (5/15 = 33%)// Mock burden: Must mock 15 methods// Verdict: OVER-BROAD ❌ // Consumer: FinanceReporting// Methods used: listTransactions, generateInvoice, getPaymentAnalytics,// exportTransactionsToCSV (4/15 = 27%)// Mock burden: Must mock 15 methods// Verdict: OVER-BROAD ❌ // RECOMMENDATION:// Split into: PaymentProcessor (3 methods), SubscriptionManager (5 methods),// InvoiceService (3 methods), PaymentAnalytics (2 methods)// Each consumer then: Uses 100% of their interface, mocks 2-5 methodsDependency burden compounds. If ServiceA depends on FatInterface (15 methods), and ServiceB depends on ServiceA, then ServiceB is transitively exposed to changes in any of those 15 methods—even if ServiceA only uses 2. Fat interfaces create invisible coupling chains throughout your codebase. Splitting interfaces breaks these chains.
Phase 5 looks forward: How will this interface evolve, and how will changes impact consumers?
Interfaces live longer than the code that creates them. An interface designed for today's requirements may become a maintenance burden as requirements evolve. This phase anticipates change trajectories and designs for graceful evolution.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
// EVOLUTION PROJECTION ANALYSIS interface NotificationService { // Stable: Core notification delivery sendEmail(recipient: string, content: EmailContent): Promise<void>; sendSMS(recipient: string, message: string): Promise<void>; sendPushNotification(userId: string, payload: PushPayload): Promise<void>; // Volatile: Channels change frequently sendSlackMessage(channel: string, message: SlackMessage): Promise<void>; sendTeamsMessage(teamId: string, message: TeamsMessage): Promise<void>; sendWhatsAppMessage(phone: string, message: string): Promise<void>; // Volatile: Preferences change with GDPR/regulations getUserPreferences(userId: string): Promise<NotificationPrefs>; updateUserPreferences(userId: string, prefs: NotificationPrefs): Promise<void>; checkConsentStatus(userId: string, channel: string): Promise<boolean>; // Volatile: Analytics requirements evolve getDeliveryStats(timeRange: TimeRange): Promise<DeliveryStats>; trackNotificationOpen(notificationId: string): Promise<void>;} // EVOLUTION ANALYSIS://// Stability Tier 1: sendEmail, sendSMS, sendPushNotification// → Mature, rarely change// // Stability Tier 2: Slack, Teams, WhatsApp// → New channels added frequently (Discord? WeChat?)// → Channel-specific message formats evolve// → Suggest: Channel-specific interfaces + factory//// Stability Tier 3: Preferences/Consent// → GDPR, CCPA, new regulations drive changes// → Suggest: Separate ConsentManager interface//// Stability Tier 4: Analytics// → Requirements change with business priorities// → Suggest: Separate NotificationAnalytics interface // PROJECTED CHANGES (next 12 months):// 1. Add Discord channel → Changes channel methods// 2. New privacy regulation → Changes consent methods// 3. New tracking requirements → Changes analytics methods// // With monolithic interface: 3 changes affect all consumers// With split interfaces: Each change affects only relevant consumersOrganize interfaces by stability: Core interfaces with stable methods change rarely and can be widely depended upon. Extension interfaces with volatile methods change frequently but are depended upon only by consumers who need that volatility. This design principle (related to the Stable Abstractions Principle) reduces the impact of change across your system.
Here's the complete checklist consolidated into an actionable reference. Use this during interface design reviews, PR reviews, and refactoring planning.
| Check | Pass Criteria | Fail Indicates |
|---|---|---|
| All consumers need all methods | Yes for >80% of consumers | Interface serves multiple roles |
| Interface has single-word role name | Yes: Creator, Reader, Validator | Conflated responsibilities |
| Method count ≤ 7 | Yes | Possible over-broad interface |
| Mock burden < 5 methods | Yes | Testing will be painful |
| Usage ratio > 50% | Method used / total > 0.5 | Consumers burdened by unused methods |
| Single stakeholder/team owns interface | Yes | Different change drivers |
| Methods change together | Yes (same PRs/features) | Incidental grouping, not cohesion |
| No empty/throwing implementations | All methods meaningful for all impls | Forced adapters required |
| Adding feature doesn't widen interface | Can add via new interface | Interface attracts unrelated growth |
| Change blast radius is limited | Change affects only relevant consumers | Unnecessary coupling |
This checklist is a diagnostic tool, not a rigid rule. Some interfaces legitimately have 10+ methods (e.g., comprehensive DTO builders). The goal is awareness: when a check fails, understand why and make a conscious decision about whether the violation is acceptable in context. Document the reasoning for future maintainers.
Here's a practical workflow for applying the ISP checklist to an interface under review:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
## ISP Review: [InterfaceName] ### Phase 1: Client AnalysisConsumers:- [ ] Consumer 1: [name] - uses methods: [list]- [ ] Consumer 2: [name] - uses methods: [list] Overlap Analysis:- Methods used by all: [list]- Methods used by subset: [list]- Methods unused: [list] **Finding**: [ ] Pass [ ] Fail - [reason] ### Phase 2: Role Identification- Single-word role name attempt: [word]- Implementation-agnostic: [ ] Yes [ ] No- Single stakeholder: [ ] Yes [ ] No - teams: [list] **Finding**: [ ] Pass [ ] Fail - [reason] ### Phase 3: Method GroupingMethod clusters identified:1. [Cluster name]: [methods]2. [Cluster name]: [methods] Cohesion indicators: [analysis] **Finding**: [ ] Pass [ ] Fail - [reason] ### Phase 4: Dependency Assessment- Method count: [number] (threshold: 7)- Average usage ratio: [percentage]- Mock burden: [number of methods] **Finding**: [ ] Pass [ ] Fail - [reason] ### Phase 5: Evolution Projection- Anticipated changes: [list]- Stability tiers: [analysis]- Blast radius assessment: [high/medium/low] **Finding**: [ ] Pass [ ] Fail - [reason] ### Decision[ ] Interface is ISP-compliant - no changes needed[ ] Interface needs splitting - proposed boundaries: - Interface A: [methods and rationale] - Interface B: [methods and rationale][ ] Accepted deviation - rationale: [explanation] Reviewed by: [name]Date: [date]You now have a systematic process for evaluating interfaces against the Interface Segregation Principle. Let's consolidate the key elements:
What's Next:
The final page of this module addresses the delicate balance between interface granularity extremes. Too few interfaces (fat interfaces) create coupling; too many interfaces (micro-interfaces) create complexity. We'll explore strategies for finding the right balance in real-world systems.
You now have a comprehensive, actionable checklist for ISP evaluation. Apply this process during interface design and code reviews to catch ISP violations before they become embedded in your architecture. Next, we'll tackle the art of balancing interface granularity.