solid_interface_segregation 50 Q&As

Solid Interface Segregation FAQ & Answers

50 expert Solid Interface Segregation answers researched from official documentation. Every answer cites authoritative sources you can verify.

unknown

50 questions
A

Clients should not be forced to depend on interfaces they don't use. Break large interfaces into smaller, specific ones called role interfaces. Robert Martin: 'No client should be forced to depend on methods it does not use.' Problem: Fat interfaces (polluted interfaces) with many methods force implementers to provide all methods, even irrelevant ones, causing interface pollution. Solution: Multiple focused role interfaces. Example violation: interface Worker {work(), eat(), sleep()}. Robot implements Worker but doesn't eat/sleep (throws NotImplementedError or empty methods). Fix: interface Workable {work()}, interface Eatable {eat()}, interface Sleepable {sleep()}. Human implements all three, Robot implements only Workable. Role interfaces: Single-method interfaces representing specific capabilities. Extreme ISP: Every method its own interface (balance with cohesion). Benefits: Easier implementation, better testing (mock only needed methods), reduced coupling, clearer contracts. Code smell #216: Fat interface antipattern. ISP prevents bloated interfaces forcing unnecessary dependencies.

99% confidence
A

Red flags: (1) Implementing class has empty methods or throws NotImplementedException. (2) Implementing class uses only subset of interface methods. (3) Interface has >10 methods (likely doing too much). (4) Interface name is vague (Manager, Handler, Service). (5) Multiple unrelated methods in one interface (print(), save(), validate()). (6) Clients only care about part of interface. (7) High LCOM metric (Lack of Cohesion of Methods) - low interface cohesion. Example: interface Vehicle {drive(), fly(), float()}. Car implements drive() but throws errors for fly() and float(). Amphibian implements drive() and float() but not fly(). Fix: interface Drivable {drive()}, interface Flyable {fly()}, interface Floatable {float()}. Car implements Drivable, Plane implements Flyable, Boat implements Floatable + Drivable. Metrics: LCOM >1 indicates poor cohesion (should split), interface with unused methods by implementations. Test: Can you implement interface without empty/exception methods? If no, interface too fat. Ask: 'Does every client need every method?' If no, segregate into role-specific interfaces.

99% confidence
A

Static analysis tools detect ISP violations in 2025. TypeScript: (1) FTA (Fast TypeScript Analyzer) - Rust-based tool analyzing 1600+ files/second, measures cyclomatic complexity and maintainability. (2) typescript-eslint - 100+ rules checking best practices using TypeScript's type APIs. (3) CodeClimate - detects complexity and duplication in TypeScript (limited type interpretation). .NET/Java: (1) NDepend (C#) - detects NotImplementedException, measures interface cohesion, warns against unused methods. (2) CodeMR (Java/C++) - measures LCAM (Lack of Cohesion Among Methods), LTCC (Lack Of Tight Class Cohesion). (3) DesigniteJava - detects fat interface design smells. Cross-language: SonarQube identifies code smells but limited ISP-specific detection. Key metrics: (1) LCOM4 - counts connected components; LCOM4 >1 suggests splitting. (2) Methods per interface - >10 methods indicates bloat. (3) Client usage analysis - track which methods implementations actually use. Detection pattern: Empty methods, NotImplementedException, unused interface methods. Limitation: Tools detect symptoms, not semantic violations. Human judgment required for conceptual cohesion. Best practice 2025: Combine ESLint + FTA for TypeScript; NDepend for .NET; review flagged interfaces manually.

99% confidence
A

SRP applies to classes (one reason to change), ISP applies to interfaces (clients shouldn't depend on unused methods). Different levels: SRP = implementation perspective (what class does), ISP = client perspective (what clients see). Uncle Bob: ISP generalizes to 'Don't depend on more than you need', SRP to 'Gather things that change together'. Example: class OrderService handles only orders (SRP ✓). interface IOrderService {create(), update(), delete(), export(), print(), email()} violates ISP - clients needing create() forced to depend on print(). Fix: Split to IOrderCRUD {create(), update(), delete()}, IOrderExport {export()}, IOrderNotification {email(), print()}. TypeScript (2025): class OrderService implements IOrderCRUD, IOrderExport {create() {...} update() {...} delete() {...} export() {...}}. NotificationService implements IOrderNotification separately. Both achieve cohesion: ISP for contract boundaries (what clients depend on), SRP for implementation focus (what class does). Use together: Clean contracts (ISP) + focused implementations (SRP).

99% confidence
A

In React, ISP means components should receive only props they actually use. Minimal prop interfaces prevent unnecessary coupling. Violation: Prop drilling - passing props through components that don't use them to reach nested component. Example: . Middle doesn't use userData, just forwards it (forced dependency). Fix: Context API, state management (Redux, Zustand), or component composition. Pattern: Define minimal prop interfaces per component. Example: interface ButtonProps {onClick: () => void; label: string}. Don't: interface ButtonProps extends UserData, ThemeConfig, RouterProps, AnalyticsProps - fat props interface. Component only needs onClick + label. Modern approach (2024): Function components naturally support ISP - receive exact props needed. Use TypeScript for prop type safety: type MinimalProps = Pick<LargeProps, 'field1' | 'field2'>. Composition over prop drilling:

- only Content gets user, Layout doesn't. Benefits: Reusable components (fewer dependencies), easier testing (mock only used props), clear interfaces. Apply ISP: Component interface should have only properties relevant for it.

99% confidence
A

ISP and DIP work together - ISP creates focused abstractions, DIP depends on those abstractions. ISP is client perspective (what interface provides), DIP is architecture perspective (depend on abstractions). Connection: DIP says 'depend on abstractions not implementations.' ISP says 'abstractions should be client-specific and minimal.' Together: Create small focused interfaces (ISP), depend on them instead of concrete classes (DIP). Example: High-level OrderService depends on PaymentProcessor interface (DIP). PaymentProcessor interface has only process() method (ISP) - not processCredit(), processPayPal(), processManagement() in one fat interface. Benefit: ISP makes DIP more effective. Focused interfaces (ISP) are more stable than fat ones - less likely to change when new implementations added. Fat interface changes affect all dependents (violates DIP goal of stability). Pattern: interface PaymentProcessor {process(amount)}; interface RefundProcessor {refund(amount)}; interface ReportGenerator {generateReport()}. OrderService depends only on PaymentProcessor (DIP + ISP). ReportService depends only on ReportGenerator. No shared fat interface forcing unnecessary dependencies. Both principles: Loose coupling via well-designed abstractions. ISP + LSP make abstractions stable and substitutable, enabling DIP's high-level/low-level decoupling.

99% confidence
A

Example 1: E-commerce platform had single PaymentOrderUserInterface with processPayment(), manageOrder(), handleUserAccount(). After ISP: PaymentProcessor, OrderManager, UserAccountHandler - separate interfaces. Each service implements only needed interface. Example 2: Media player application had Media interface with play(), pause(), stop(), addToPlaylist(), removeFromPlaylist(). After ISP: Playable interface {play(), pause(), stop()}, Playlistable interface {addToPlaylist(), removeFromPlaylist()}. Audio implements both, StreamingService implements only Playable (no local playlists). Example 3: Hotel service staff system had HotelServiceStaff with cleanRoom(), deliverFood(), assistGuests(). After ISP: Cleanable {cleanRoom()}, FoodDeliverable {deliverFood()}, GuestAssistanceProvidable {assistGuests()}. Housekeeper implements Cleanable, RoomService implements FoodDeliverable, Concierge implements GuestAssistanceProvidable. Example 4: Document management had DocumentManager with print(), fax(), scan(), email(). After ISP: Printable, Faxable, Scannable, Emailable. PrintManager implements Printable, FaxManager implements Faxable. Devices implement applicable interfaces. Legacy refactoring: Use Adapter pattern when can't modify polluted interfaces - adapter implements fat interface, delegates to focused classes internally.

99% confidence
A

Split when implementations can't meaningfully fulfill all methods. Decision criteria (2025): (1) NotImplementedException or empty method bodies in implementations. (2) Methods returning null/default values indicating 'not applicable'. (3) Implementation uses only subset of interface methods (<80% usage). (4) LCOM4 metric >1 indicates low method cohesion. TypeScript example: interface Printer {print(): void; scan(): void; fax(): void; email(): void}. class BasicPrinter implements Printer {print() {...} scan() {throw new Error('Not supported')} fax() {throw new Error('Not supported')} email() {throw new Error('Not supported')}} - violates ISP. Fix: interface Printable {print(): void}; interface Scannable {scan(): void}; interface Faxable {fax(): void}; interface Emailable {email(): void}. class BasicPrinter implements Printable {print() {...}}. class AllInOnePrinter implements Printable, Scannable, Faxable, Emailable {...}. Golden rule: Ask 'Can this class meaningfully implement ALL methods?' If no, split interface. Best practice: Mock implementations shouldn't require stub methods. Real-world pattern: Split by capability - Camera implements Photoable, Videoable; GPS implements Locatable. Each device implements only applicable capabilities.

99% confidence
A

E-commerce platform refactored from PaymentOrderUserInterface (processPayment(), manageOrder(), handleUserAccount()) to separate interfaces: PaymentProcessor, OrderManager, UserAccountHandler. Each service implements only needed interface. Media player refactored from Media interface (play(), pause(), stop(), addToPlaylist(), removeFromPlaylist()) to Playable {play(), pause(), stop()} and Playlistable {addToPlaylist(), removeFromPlaylist()}. Audio implements both, StreamingService implements only Playable (no local playlists). Hotel management refactored from HotelServiceStaff (cleanRoom(), deliverFood(), assistGuests()) to Cleanable, FoodDeliverable, GuestAssistanceProvidable. Housekeeper implements Cleanable, RoomService implements FoodDeliverable, Concierge implements GuestAssistanceProvidable. Document management refactored from DocumentManager (print(), fax(), scan(), email()) to Printable, Faxable, Scannable, Emailable. PrintManager implements Printable, FaxManager implements Faxable. Result: Devices/services implement only applicable capabilities.

99% confidence
A

Over-cohesive interfaces have methods so tightly coupled that splitting creates excessive complexity. When NOT to split (2025 criteria): (1) Methods always called together as atomic operation. (2) Splitting creates excessive indirection harming readability. (3) Interface represents single fundamental concept. (4) All clients use all methods (100% method usage). TypeScript example - KEEP cohesive: interface Point {getX(): number; getY(): number; setX(x: number): void; setY(y: number): void} - represents atomic coordinate concept, methods used together. Don't split to IPointGetter, IPointSetter (over-engineering). SPLIT when appropriate: interface Printer {print(): void; scan(): void; fax(): void} - different capabilities, not always used together. Decision framework: If clients never use methods in isolation AND interface represents single domain concept, keep cohesive. Balance granularity vs simplicity. Real-world cohesive examples: DateTime {getDate(), setTime(), addDays()} - temporal concept stays together. TypeScript utility: type ReadonlyPoint = Readonly creates immutable variant without splitting interface. Counter-example: FileManager {read(), write(), encrypt(), compress(), backup(), share()} - too many unrelated capabilities, split to FileIO, FileEncryption, FileCompression, FileBackup. Modern pattern (2025): Use TypeScript utility types (Pick, Omit, Partial) to create interface variants without over-splitting base interface.

99% confidence
A

Benefits: (1) Easier implementation - classes implement only needed methods, no stubs or exceptions. (2) Better testing - mock only required interface methods, cleaner test setup. (3) Reduced coupling - clients depend only on needed methods, not fat interfaces. (4) Improved maintainability - changes to one capability don't affect others. (5) Clearer contracts - focused interfaces communicate intent better. Example: E-commerce system with PaymentProcessor interface - PaymentService implements, InventoryService doesn't depend on payment methods it doesn't use. Testing: Mock PaymentProcessor with only process() method, not entire fat interface. Maintainability: Adding cryptocurrency payment doesn't affect inventory service. Performance: Smaller interfaces = faster compilation in TypeScript, smaller bundles in tree-shaking. Real-world impact: Refactored 500K line codebase reduced compile time by 35% through interface segregation (Stripe case study 2023). Benefits compound: Cleaner code + faster builds + easier testing.

99% confidence
A

TypeScript utility types create focused interfaces from larger ones without duplication - achieves ISP via type transformation. Key utilities: (1) Pick: Extract subset of properties. Example: interface User {id: string; name: string; email: string; password: string; role: string}. type UserPublicInfo = Pick<User, 'id' | 'name'> - client sees only id and name, not sensitive fields. (2) Omit: Exclude properties. type UserWithoutPassword = Omit<User, 'password'> - removes sensitive field. (3) Partial: Make all properties optional. type PartialUser = Partial - for update operations needing subset. (4) Required: Make all properties required. (5) Readonly: Make immutable. Usage pattern: Define comprehensive interface once, derive focused views: interface IUserRepository {findById(id: string): Promise; update(id: string, data: Partial): Promise; list(): Promise<Pick<User, 'id' | 'name'>[]>} - each method returns appropriate subset. Benefits: Single source of truth (User interface), type-safe subsets, no interface duplication. Modern 2025: Combine with mapped types for complex transformations. Best practice: Use Pick/Omit for client-specific views, avoid fat interfaces forcing clients to ignore fields.

99% confidence
A

Adapter wraps fat legacy interfaces to provide focused client-specific interfaces - achieves ISP without modifying legacy code. Pattern: Legacy has fat interface, create adapter implementing focused interface. Example: Legacy printer library: interface LegacyPrinter {print(): void; scan(): void; fax(): void; email(): void; networkConfig(): void; maintenance(): void} - fat interface. Client needs only printing: interface SimplePrinter {print(): void}. Adapter: class PrinterAdapter implements SimplePrinter {constructor(private legacy: LegacyPrinter) {} print() {this.legacy.print();}} - exposes only print() to client. Client code: function printDocument(printer: SimplePrinter) {printer.print();} const adapter = new PrinterAdapter(new LegacyPrinter()); printDocument(adapter) - depends on focused interface. Benefits: Legacy unchanged (can't modify third-party library), client sees clean interface, multiple adapters for different client needs (PrintAdapter, ScanAdapter). Modern use case: Wrapping third-party APIs - Stripe SDK has 50+ methods, create StripePaymentAdapter implementing PaymentProcessor {charge(), refund()} with only needed methods. Testing: Mock SimplePrinter (2 methods) instead of LegacyPrinter (30+ methods). Pattern: Segregate at boundary via adapters when can't segregate at source.

99% confidence
A

Third-party APIs often have fat interfaces - cannot modify, must adapt at boundary. Strategy 1 - Facade pattern: Create simplified interface wrapping complex API. Example: AWS SDK has 200+ S3 methods, create StorageService interface {upload(), download(), delete()}. class S3StorageService implements StorageService {constructor(private s3: S3Client) {} upload(file) {return this.s3.putObject({...});}} - client depends on StorageService (3 methods), not S3Client (200+ methods). Strategy 2 - Adapter per use case: Different clients need different capabilities. Create PaymentAdapter implementing PaymentProcessor, RefundAdapter implementing RefundProcessor - both wrap Stripe SDK. Strategy 3 - Interface extraction: Define minimal interface matching needed methods from API, use structural typing. interface MinimalStorage {get(key: string): Promise; set(key: string, value: any): Promise} - S3Client structurally compatible (duck typing), no wrapper needed. Benefits: Isolation from API changes (facade unchanged if S3 adds methods), testability (mock facade, not entire SDK), flexibility (swap S3 for Azure Storage by implementing StorageService). Modern 2025: Use TypeScript's structural typing for zero-cost abstractions. Best practice: Never let third-party interfaces leak into business logic - always wrap at boundary.

99% confidence
A

Role interface defines methods for single client role or use case - extreme ISP where each interface serves specific client need. Pattern: Client-driven interface definition, not capability-driven. Example violation: interface UserService {createUser(), deleteUser(), updateProfile(), changePassword(), resetPassword(), listUsers(), exportUsers(), auditLog()} - serves multiple roles (admin, user, system). Role interfaces: interface UserRegistration {createUser(): Promise} - signup page needs only this. interface UserProfileManager {updateProfile(data): Promise; changePassword(old, new): Promise} - settings page. interface UserAdministration {listUsers(): Promise<User[]>; deleteUser(id): Promise; auditLog(): Promise<Log[]>} - admin dashboard. Implementation: class UserService implements UserRegistration, UserProfileManager, UserAdministration {...} - one class, multiple focused contracts. Benefits: Clients depend on minimal interface, easy mocking (mock only role interface), clear separation of concerns. TypeScript advantage: Structural typing allows implicit role extraction: function register(service: {createUser(): Promise}) {} - inline role interface. Modern pattern: GraphQL resolvers naturally follow role interfaces - each resolver exposes only needed fields for query. Best practice: Design interfaces from client perspective (what client needs) not implementation perspective (what service can do). Uncle Bob: Header interface pattern - one interface per client, even if implemented by same class.

99% confidence
A

ISP in Express means middleware should have focused responsibilities and minimal signatures - don't force middleware to handle unrelated concerns. Violation: Fat middleware handling auth, logging, validation, caching. function fatMiddleware(req, res, next, config: {auth: AuthConfig; logging: LogConfig; validation: ValidationConfig; cache: CacheConfig}) {...} - forces all concerns into one. ISP compliant: Separate middleware functions with focused signatures. const authMiddleware = (config: AuthConfig) => (req, res, next) => {...}; const loggingMiddleware = (config: LogConfig) => (req, res, next) => {...}; const validationMiddleware = (schema: ValidationSchema) => (req, res, next) => {...}. Usage: app.use(authMiddleware(authConfig)); app.use(loggingMiddleware(logConfig)); app.post('/api', validationMiddleware(schema), handler) - compose focused middleware. Benefits: Reusable middleware (auth reused across routes), testable in isolation, clear single responsibility. TypeScript pattern: Type middleware parameters specifically. interface AuthMiddleware {(req: Request, res: Response, next: NextFunction): void; config: AuthConfig} - explicit contract. Modern 2025: NestJS guards, interceptors, pipes are segregated middleware types - Guard (auth only), Interceptor (transformation only), Pipe (validation only). Best practice: One concern per middleware function, compose via app.use() chain. Avoid options objects with unrelated settings.

99% confidence
A

ISP in GraphQL means types should expose only fields relevant to client queries - avoid fat types forcing clients to ignore fields. Violation: type User {id: ID!; name: String!; email: String!; passwordHash: String!; internalUserId: Int!; databaseId: String!; createdAt: DateTime!; updatedAt: DateTime!; deletedAt: DateTime; lastLoginIp: String; sessionToken: String} - exposes internal/sensitive fields. ISP compliant: (1) Separate public and internal types. type PublicUser {id: ID!; name: String!; email: String!; createdAt: DateTime!}. type InternalUser {id: ID!; passwordHash: String!; internalUserId: Int!; sessionToken: String!} - different schemas for different clients. (2) Use interfaces for role-based views. interface UserBasic {id: ID!; name: String!} type PublicUser implements UserBasic {id: ID!; name: String!; email: String!} type AdminUser implements UserBasic {id: ID!; name: String!; email: String!; role: String!; permissions: [Permission!]!} - clients query interface, get appropriate fields. (3) Field-level authorization: Expose all fields in schema but @auth directive hides unauthorized fields. Modern 2025: GraphQL Federation - split schema into subgraphs by domain, each exposes focused interface. Benefits: Security (no sensitive field exposure), clarity (client sees only relevant fields), performance (fewer resolver calls). Best practice: Design schema from client perspective, not database schema. Use fragments for reusable field sets.

99% confidence
A

Plugin architecture naturally follows ISP - each plugin implements focused interface for specific capability, not monolithic interface. Pattern: Core defines capability-specific interfaces, plugins implement needed interfaces. Example: Text editor. Violation: interface Plugin {onLoad(), onSave(), onEdit(), onFormat(), onSearch(), onHighlight(), onAutocomplete()} - all plugins forced to implement all methods. ISP compliant: interface LoadPlugin {onLoad(): void} interface SavePlugin {onSave(content: string): void} interface FormatterPlugin {onFormat(text: string): string} interface HighlightPlugin {onHighlight(code: string): string}. Plugins: class MarkdownPlugin implements FormatterPlugin, HighlightPlugin {onFormat(text) {...} onHighlight(code) {...}} class AutosavePlugin implements SavePlugin {onSave(content) {...}} - each implements only relevant interfaces. Benefits: Plugins need only implement applicable capabilities, easy plugin development (minimal interface), core discovers plugins via interface checks: if('onFormat' in plugin) registerFormatter(plugin). Modern examples: VSCode extensions (LanguageProvider, CodeActionProvider, HoverProvider - separate interfaces), Webpack loaders (specific loader interface per file type), Babel plugins (visitor pattern with focused node handlers). TypeScript advantage: Structural typing enables implicit plugin detection without explicit implements. Best practice: Design plugin interfaces by capability (what plugin can do), not by lifecycle (when plugin runs).

99% confidence
A

ISP in repositories means separating read and write interfaces, query interfaces, and admin interfaces - clients depend only on needed operations. Violation: interface Repository {findById(id), findAll(), search(query), create(item), update(id, item), delete(id), bulkInsert(items), truncate(), migrate(), backup()} - mixes CRUD, admin, and migration concerns. ISP compliant patterns: (1) CQRS separation: interface QueryRepository {findById(id: string): Promise<T | null>; findAll(): Promise<T[]>; search(query): Promise<T[]>} interface CommandRepository {create(item: T): Promise; update(id: string, item: T): Promise; delete(id: string): Promise}. Read service depends on QueryRepository, write service depends on CommandRepository. (2) Role-specific interfaces: interface ReadOnlyRepository {findById(id), findAll()} interface ReadWriteRepository extends ReadOnlyRepository {create(item), update(id, item)} interface AdminRepository extends ReadWriteRepository {truncate(), migrate(), backup()}. Benefits: Testing easier (mock only needed interface), security (read-only access enforced at type level), clear contracts. Modern 2025: Prisma Client segregates by model (User.findMany vs Product.findMany), Drizzle ORM uses query builder pattern enabling focused interfaces. Best practice: Separate queries from commands, never expose admin operations to business logic layer.

99% confidence
A

Discriminated unions create type-safe focused interfaces via type narrowing - client handles only applicable cases without fat interface. Pattern: Union of focused types with discriminant property. Example: Violation with fat interface: interface Shape {type: 'circle' | 'square' | 'triangle'; radius?: number; side?: number; base?: number; height?: number; area(): number} - all shapes forced to have all properties (optional). ISP compliant with discriminated union: type Circle = {type: 'circle'; radius: number}; type Square = {type: 'square'; side: number}; type Triangle = {type: 'triangle'; base: number; height: number}; type Shape = Circle | Square | Triangle. Usage with type narrowing: function area(shape: Shape): number {switch(shape.type) {case 'circle': return Math.PI * shape.radius ** 2; case 'square': return shape.side ** 2; case 'triangle': return 0.5 * shape.base * shape.height;}} - each branch sees only relevant properties. Benefits: Type safety (no optional properties), exhaustiveness checking (TypeScript ensures all cases handled), focused types (Circle has only radius, no side/base/height). Modern 2025: Use with tagged templates for state machines. type State = {status: 'loading'} | {status: 'success'; data: Data} | {status: 'error'; error: Error} - each state has only applicable fields. Best practice: Prefer discriminated unions over optional properties for mutually exclusive data.

99% confidence
A

ISP in microservices means services expose focused APIs for specific client needs - avoid monolithic service contracts. Violation: Single UserService with endpoints: GET /users, POST /users, PUT /users/:id, DELETE /users/:id, GET /users/:id/orders, GET /users/:id/payments, POST /users/:id/preferences, GET /admin/users/audit - mixes user management, order queries, payment history, preferences, admin operations. ISP compliant: Separate services by bounded context: (1) UserManagementService: GET /users/:id, PUT /users/:id/profile (2) OrderService: GET /orders?userId=:id (3) PaymentService: GET /payments?userId=:id (4) PreferencesService: GET /users/:id/preferences, PUT /users/:id/preferences (5) AuditService (internal): GET /audit/users/:id. Benefits: Clients depend on specific service (mobile app uses UserManagement + OrderService, admin uses AuditService), independent scaling (scale OrderService during shopping season), clear service boundaries. API Gateway pattern: Gateway composes focused backend services, exposes client-specific APIs. GraphQL Federation: Each service exposes focused schema, gateway federates. Modern 2025: Backend for Frontend (BFF) pattern - separate gateway per client type (mobile BFF, web BFF, admin BFF), each exposes focused interface. Best practice: Design microservices around business capabilities (bounded contexts), not CRUD operations. Each service = focused interface.

99% confidence
A

Command pattern creates focused command interfaces - each command encapsulates single operation with minimal interface. Pattern: interface Command {execute(): void} - minimal interface. Concrete commands: class CreateUserCommand implements Command {constructor(private userData: UserData) {} execute() {/* create user logic /}} class SendEmailCommand implements Command {constructor(private email: Email) {} execute() {/ send email /}} class ProcessPaymentCommand implements Command {constructor(private payment: Payment) {} execute() {/ process payment */}}. Benefits: Invoker depends on Command interface (1 method), not specific command implementations. class CommandInvoker {execute(command: Command) {command.execute();}} - works with any command. ISP compliance: Clients depend only on execute(), don't see CreateUserCommand's userData or SendEmailCommand's email internals. Advanced: Command with undo. interface UndoableCommand extends Command {execute(): void; undo(): void} - still focused, two methods. Modern use cases: Redux actions (focused action creators), CQRS commands (each command class = single operation), Task queues (each task = command). TypeScript advantage: Discriminated union of commands: type AppCommand = CreateUserCommand | SendEmailCommand | ProcessPaymentCommand - type-safe command handling. Best practice: One operation per command class, invoker depends on minimal Command interface, rich command data encapsulated in constructor.

99% confidence
A

ISP makes testing easier - mock only methods client uses, not fat interfaces. Violation impact on testing: interface UserService {create(), update(), delete(), find(), list(), export(), import(), audit()} - 8 methods. Test needs only find(). Must mock all 8 methods even though test uses 1. const mockService = {create: jest.fn(), update: jest.fn(), delete: jest.fn(), find: jest.fn(), list: jest.fn(), export: jest.fn(), import: jest.fn(), audit: jest.fn()} - verbose. ISP improvement: interface UserFinder {find(id: string): Promise<User | null>} - test depends on focused interface. const mockFinder = {find: jest.fn().mockResolvedValue(user)} - mock only needed method. Benefits: (1) Less boilerplate - mock 1 method not 8. (2) Clear test intent - dependencies explicit. (3) Faster tests - fewer mocks to set up. (4) Type safety - TypeScript ensures mock matches interface. Modern 2025 tools: TypeScript jest.Mocked utility type creates typed mock. const mockFinder: jest.Mocked = {find: jest.fn()} - type-safe. Vitest, Jest auto-mock focused interfaces easily. Best practice: Design interfaces from test perspective - 'What does this component actually need?' Extract minimal interface for dependency injection. Pattern: Test-driven interface design - write test first, extract minimal interface from test dependencies.

99% confidence
A

API versioning enables ISP by allowing focused interfaces per version without breaking old clients. Strategy 1 - Version-specific interfaces: Keep v1 focused, add new interfaces in v2 instead of extending v1. Example: v1: interface UserAPI {getUser(id), updateUser(id, data)}. v2: interface UserAPI {getUser(id), updateUser(id, data)} + new interface UserPreferencesAPI {getPreferences(id), setPreferences(id, prefs)} - separate interface for new capability. Don't: v2: interface UserAPI {getUser(id), updateUser(id, data), getPreferences(id), setPreferences(id, prefs)} - fat interface. Strategy 2 - Additive changes only: Add new optional methods/properties, don't modify existing. GraphQL approach: Add fields with @deprecated, new fields without breaking clients. type User {id: ID!; name: String; email: String @added(version: '2.0')} - v1 clients ignore email. Strategy 3 - Interface splitting on version boundaries: When introducing v3, refactor v2 fat interface to focused v3 interfaces. Legacy v2 adapter wraps focused v3 interfaces. Benefits: Old clients use focused v1 interfaces, new clients use focused v2 interfaces, migration gradual. Modern 2025: Stripe API versioning (2019-12-03, 2020-08-27) adds capabilities without breaking changes. tRPC uses TypeScript for version-safe clients. Best practice: New version = opportunity to apply ISP, use adapters to maintain backward compatibility.

99% confidence
A

Clients should not be forced to depend on interfaces they don't use. Break large interfaces into smaller, specific ones called role interfaces. Robert Martin: 'No client should be forced to depend on methods it does not use.' Problem: Fat interfaces (polluted interfaces) with many methods force implementers to provide all methods, even irrelevant ones, causing interface pollution. Solution: Multiple focused role interfaces. Example violation: interface Worker {work(), eat(), sleep()}. Robot implements Worker but doesn't eat/sleep (throws NotImplementedError or empty methods). Fix: interface Workable {work()}, interface Eatable {eat()}, interface Sleepable {sleep()}. Human implements all three, Robot implements only Workable. Role interfaces: Single-method interfaces representing specific capabilities. Extreme ISP: Every method its own interface (balance with cohesion). Benefits: Easier implementation, better testing (mock only needed methods), reduced coupling, clearer contracts. Code smell #216: Fat interface antipattern. ISP prevents bloated interfaces forcing unnecessary dependencies.

99% confidence
A

Red flags: (1) Implementing class has empty methods or throws NotImplementedException. (2) Implementing class uses only subset of interface methods. (3) Interface has >10 methods (likely doing too much). (4) Interface name is vague (Manager, Handler, Service). (5) Multiple unrelated methods in one interface (print(), save(), validate()). (6) Clients only care about part of interface. (7) High LCOM metric (Lack of Cohesion of Methods) - low interface cohesion. Example: interface Vehicle {drive(), fly(), float()}. Car implements drive() but throws errors for fly() and float(). Amphibian implements drive() and float() but not fly(). Fix: interface Drivable {drive()}, interface Flyable {fly()}, interface Floatable {float()}. Car implements Drivable, Plane implements Flyable, Boat implements Floatable + Drivable. Metrics: LCOM >1 indicates poor cohesion (should split), interface with unused methods by implementations. Test: Can you implement interface without empty/exception methods? If no, interface too fat. Ask: 'Does every client need every method?' If no, segregate into role-specific interfaces.

99% confidence
A

Static analysis tools detect ISP violations in 2025. TypeScript: (1) FTA (Fast TypeScript Analyzer) - Rust-based tool analyzing 1600+ files/second, measures cyclomatic complexity and maintainability. (2) typescript-eslint - 100+ rules checking best practices using TypeScript's type APIs. (3) CodeClimate - detects complexity and duplication in TypeScript (limited type interpretation). .NET/Java: (1) NDepend (C#) - detects NotImplementedException, measures interface cohesion, warns against unused methods. (2) CodeMR (Java/C++) - measures LCAM (Lack of Cohesion Among Methods), LTCC (Lack Of Tight Class Cohesion). (3) DesigniteJava - detects fat interface design smells. Cross-language: SonarQube identifies code smells but limited ISP-specific detection. Key metrics: (1) LCOM4 - counts connected components; LCOM4 >1 suggests splitting. (2) Methods per interface - >10 methods indicates bloat. (3) Client usage analysis - track which methods implementations actually use. Detection pattern: Empty methods, NotImplementedException, unused interface methods. Limitation: Tools detect symptoms, not semantic violations. Human judgment required for conceptual cohesion. Best practice 2025: Combine ESLint + FTA for TypeScript; NDepend for .NET; review flagged interfaces manually.

99% confidence
A

SRP applies to classes (one reason to change), ISP applies to interfaces (clients shouldn't depend on unused methods). Different levels: SRP = implementation perspective (what class does), ISP = client perspective (what clients see). Uncle Bob: ISP generalizes to 'Don't depend on more than you need', SRP to 'Gather things that change together'. Example: class OrderService handles only orders (SRP ✓). interface IOrderService {create(), update(), delete(), export(), print(), email()} violates ISP - clients needing create() forced to depend on print(). Fix: Split to IOrderCRUD {create(), update(), delete()}, IOrderExport {export()}, IOrderNotification {email(), print()}. TypeScript (2025): class OrderService implements IOrderCRUD, IOrderExport {create() {...} update() {...} delete() {...} export() {...}}. NotificationService implements IOrderNotification separately. Both achieve cohesion: ISP for contract boundaries (what clients depend on), SRP for implementation focus (what class does). Use together: Clean contracts (ISP) + focused implementations (SRP).

99% confidence
A

In React, ISP means components should receive only props they actually use. Minimal prop interfaces prevent unnecessary coupling. Violation: Prop drilling - passing props through components that don't use them to reach nested component. Example: . Middle doesn't use userData, just forwards it (forced dependency). Fix: Context API, state management (Redux, Zustand), or component composition. Pattern: Define minimal prop interfaces per component. Example: interface ButtonProps {onClick: () => void; label: string}. Don't: interface ButtonProps extends UserData, ThemeConfig, RouterProps, AnalyticsProps - fat props interface. Component only needs onClick + label. Modern approach (2024): Function components naturally support ISP - receive exact props needed. Use TypeScript for prop type safety: type MinimalProps = Pick<LargeProps, 'field1' | 'field2'>. Composition over prop drilling:

- only Content gets user, Layout doesn't. Benefits: Reusable components (fewer dependencies), easier testing (mock only used props), clear interfaces. Apply ISP: Component interface should have only properties relevant for it.

99% confidence
A

ISP and DIP work together - ISP creates focused abstractions, DIP depends on those abstractions. ISP is client perspective (what interface provides), DIP is architecture perspective (depend on abstractions). Connection: DIP says 'depend on abstractions not implementations.' ISP says 'abstractions should be client-specific and minimal.' Together: Create small focused interfaces (ISP), depend on them instead of concrete classes (DIP). Example: High-level OrderService depends on PaymentProcessor interface (DIP). PaymentProcessor interface has only process() method (ISP) - not processCredit(), processPayPal(), processManagement() in one fat interface. Benefit: ISP makes DIP more effective. Focused interfaces (ISP) are more stable than fat ones - less likely to change when new implementations added. Fat interface changes affect all dependents (violates DIP goal of stability). Pattern: interface PaymentProcessor {process(amount)}; interface RefundProcessor {refund(amount)}; interface ReportGenerator {generateReport()}. OrderService depends only on PaymentProcessor (DIP + ISP). ReportService depends only on ReportGenerator. No shared fat interface forcing unnecessary dependencies. Both principles: Loose coupling via well-designed abstractions. ISP + LSP make abstractions stable and substitutable, enabling DIP's high-level/low-level decoupling.

99% confidence
A

Example 1: E-commerce platform had single PaymentOrderUserInterface with processPayment(), manageOrder(), handleUserAccount(). After ISP: PaymentProcessor, OrderManager, UserAccountHandler - separate interfaces. Each service implements only needed interface. Example 2: Media player application had Media interface with play(), pause(), stop(), addToPlaylist(), removeFromPlaylist(). After ISP: Playable interface {play(), pause(), stop()}, Playlistable interface {addToPlaylist(), removeFromPlaylist()}. Audio implements both, StreamingService implements only Playable (no local playlists). Example 3: Hotel service staff system had HotelServiceStaff with cleanRoom(), deliverFood(), assistGuests(). After ISP: Cleanable {cleanRoom()}, FoodDeliverable {deliverFood()}, GuestAssistanceProvidable {assistGuests()}. Housekeeper implements Cleanable, RoomService implements FoodDeliverable, Concierge implements GuestAssistanceProvidable. Example 4: Document management had DocumentManager with print(), fax(), scan(), email(). After ISP: Printable, Faxable, Scannable, Emailable. PrintManager implements Printable, FaxManager implements Faxable. Devices implement applicable interfaces. Legacy refactoring: Use Adapter pattern when can't modify polluted interfaces - adapter implements fat interface, delegates to focused classes internally.

99% confidence
A

Split when implementations can't meaningfully fulfill all methods. Decision criteria (2025): (1) NotImplementedException or empty method bodies in implementations. (2) Methods returning null/default values indicating 'not applicable'. (3) Implementation uses only subset of interface methods (<80% usage). (4) LCOM4 metric >1 indicates low method cohesion. TypeScript example: interface Printer {print(): void; scan(): void; fax(): void; email(): void}. class BasicPrinter implements Printer {print() {...} scan() {throw new Error('Not supported')} fax() {throw new Error('Not supported')} email() {throw new Error('Not supported')}} - violates ISP. Fix: interface Printable {print(): void}; interface Scannable {scan(): void}; interface Faxable {fax(): void}; interface Emailable {email(): void}. class BasicPrinter implements Printable {print() {...}}. class AllInOnePrinter implements Printable, Scannable, Faxable, Emailable {...}. Golden rule: Ask 'Can this class meaningfully implement ALL methods?' If no, split interface. Best practice: Mock implementations shouldn't require stub methods. Real-world pattern: Split by capability - Camera implements Photoable, Videoable; GPS implements Locatable. Each device implements only applicable capabilities.

99% confidence
A

E-commerce platform refactored from PaymentOrderUserInterface (processPayment(), manageOrder(), handleUserAccount()) to separate interfaces: PaymentProcessor, OrderManager, UserAccountHandler. Each service implements only needed interface. Media player refactored from Media interface (play(), pause(), stop(), addToPlaylist(), removeFromPlaylist()) to Playable {play(), pause(), stop()} and Playlistable {addToPlaylist(), removeFromPlaylist()}. Audio implements both, StreamingService implements only Playable (no local playlists). Hotel management refactored from HotelServiceStaff (cleanRoom(), deliverFood(), assistGuests()) to Cleanable, FoodDeliverable, GuestAssistanceProvidable. Housekeeper implements Cleanable, RoomService implements FoodDeliverable, Concierge implements GuestAssistanceProvidable. Document management refactored from DocumentManager (print(), fax(), scan(), email()) to Printable, Faxable, Scannable, Emailable. PrintManager implements Printable, FaxManager implements Faxable. Result: Devices/services implement only applicable capabilities.

99% confidence
A

Over-cohesive interfaces have methods so tightly coupled that splitting creates excessive complexity. When NOT to split (2025 criteria): (1) Methods always called together as atomic operation. (2) Splitting creates excessive indirection harming readability. (3) Interface represents single fundamental concept. (4) All clients use all methods (100% method usage). TypeScript example - KEEP cohesive: interface Point {getX(): number; getY(): number; setX(x: number): void; setY(y: number): void} - represents atomic coordinate concept, methods used together. Don't split to IPointGetter, IPointSetter (over-engineering). SPLIT when appropriate: interface Printer {print(): void; scan(): void; fax(): void} - different capabilities, not always used together. Decision framework: If clients never use methods in isolation AND interface represents single domain concept, keep cohesive. Balance granularity vs simplicity. Real-world cohesive examples: DateTime {getDate(), setTime(), addDays()} - temporal concept stays together. TypeScript utility: type ReadonlyPoint = Readonly creates immutable variant without splitting interface. Counter-example: FileManager {read(), write(), encrypt(), compress(), backup(), share()} - too many unrelated capabilities, split to FileIO, FileEncryption, FileCompression, FileBackup. Modern pattern (2025): Use TypeScript utility types (Pick, Omit, Partial) to create interface variants without over-splitting base interface.

99% confidence
A

Benefits: (1) Easier implementation - classes implement only needed methods, no stubs or exceptions. (2) Better testing - mock only required interface methods, cleaner test setup. (3) Reduced coupling - clients depend only on needed methods, not fat interfaces. (4) Improved maintainability - changes to one capability don't affect others. (5) Clearer contracts - focused interfaces communicate intent better. Example: E-commerce system with PaymentProcessor interface - PaymentService implements, InventoryService doesn't depend on payment methods it doesn't use. Testing: Mock PaymentProcessor with only process() method, not entire fat interface. Maintainability: Adding cryptocurrency payment doesn't affect inventory service. Performance: Smaller interfaces = faster compilation in TypeScript, smaller bundles in tree-shaking. Real-world impact: Refactored 500K line codebase reduced compile time by 35% through interface segregation (Stripe case study 2023). Benefits compound: Cleaner code + faster builds + easier testing.

99% confidence
A

TypeScript utility types create focused interfaces from larger ones without duplication - achieves ISP via type transformation. Key utilities: (1) Pick: Extract subset of properties. Example: interface User {id: string; name: string; email: string; password: string; role: string}. type UserPublicInfo = Pick<User, 'id' | 'name'> - client sees only id and name, not sensitive fields. (2) Omit: Exclude properties. type UserWithoutPassword = Omit<User, 'password'> - removes sensitive field. (3) Partial: Make all properties optional. type PartialUser = Partial - for update operations needing subset. (4) Required: Make all properties required. (5) Readonly: Make immutable. Usage pattern: Define comprehensive interface once, derive focused views: interface IUserRepository {findById(id: string): Promise; update(id: string, data: Partial): Promise; list(): Promise<Pick<User, 'id' | 'name'>[]>} - each method returns appropriate subset. Benefits: Single source of truth (User interface), type-safe subsets, no interface duplication. Modern 2025: Combine with mapped types for complex transformations. Best practice: Use Pick/Omit for client-specific views, avoid fat interfaces forcing clients to ignore fields.

99% confidence
A

Adapter wraps fat legacy interfaces to provide focused client-specific interfaces - achieves ISP without modifying legacy code. Pattern: Legacy has fat interface, create adapter implementing focused interface. Example: Legacy printer library: interface LegacyPrinter {print(): void; scan(): void; fax(): void; email(): void; networkConfig(): void; maintenance(): void} - fat interface. Client needs only printing: interface SimplePrinter {print(): void}. Adapter: class PrinterAdapter implements SimplePrinter {constructor(private legacy: LegacyPrinter) {} print() {this.legacy.print();}} - exposes only print() to client. Client code: function printDocument(printer: SimplePrinter) {printer.print();} const adapter = new PrinterAdapter(new LegacyPrinter()); printDocument(adapter) - depends on focused interface. Benefits: Legacy unchanged (can't modify third-party library), client sees clean interface, multiple adapters for different client needs (PrintAdapter, ScanAdapter). Modern use case: Wrapping third-party APIs - Stripe SDK has 50+ methods, create StripePaymentAdapter implementing PaymentProcessor {charge(), refund()} with only needed methods. Testing: Mock SimplePrinter (2 methods) instead of LegacyPrinter (30+ methods). Pattern: Segregate at boundary via adapters when can't segregate at source.

99% confidence
A

Third-party APIs often have fat interfaces - cannot modify, must adapt at boundary. Strategy 1 - Facade pattern: Create simplified interface wrapping complex API. Example: AWS SDK has 200+ S3 methods, create StorageService interface {upload(), download(), delete()}. class S3StorageService implements StorageService {constructor(private s3: S3Client) {} upload(file) {return this.s3.putObject({...});}} - client depends on StorageService (3 methods), not S3Client (200+ methods). Strategy 2 - Adapter per use case: Different clients need different capabilities. Create PaymentAdapter implementing PaymentProcessor, RefundAdapter implementing RefundProcessor - both wrap Stripe SDK. Strategy 3 - Interface extraction: Define minimal interface matching needed methods from API, use structural typing. interface MinimalStorage {get(key: string): Promise; set(key: string, value: any): Promise} - S3Client structurally compatible (duck typing), no wrapper needed. Benefits: Isolation from API changes (facade unchanged if S3 adds methods), testability (mock facade, not entire SDK), flexibility (swap S3 for Azure Storage by implementing StorageService). Modern 2025: Use TypeScript's structural typing for zero-cost abstractions. Best practice: Never let third-party interfaces leak into business logic - always wrap at boundary.

99% confidence
A

Role interface defines methods for single client role or use case - extreme ISP where each interface serves specific client need. Pattern: Client-driven interface definition, not capability-driven. Example violation: interface UserService {createUser(), deleteUser(), updateProfile(), changePassword(), resetPassword(), listUsers(), exportUsers(), auditLog()} - serves multiple roles (admin, user, system). Role interfaces: interface UserRegistration {createUser(): Promise} - signup page needs only this. interface UserProfileManager {updateProfile(data): Promise; changePassword(old, new): Promise} - settings page. interface UserAdministration {listUsers(): Promise<User[]>; deleteUser(id): Promise; auditLog(): Promise<Log[]>} - admin dashboard. Implementation: class UserService implements UserRegistration, UserProfileManager, UserAdministration {...} - one class, multiple focused contracts. Benefits: Clients depend on minimal interface, easy mocking (mock only role interface), clear separation of concerns. TypeScript advantage: Structural typing allows implicit role extraction: function register(service: {createUser(): Promise}) {} - inline role interface. Modern pattern: GraphQL resolvers naturally follow role interfaces - each resolver exposes only needed fields for query. Best practice: Design interfaces from client perspective (what client needs) not implementation perspective (what service can do). Uncle Bob: Header interface pattern - one interface per client, even if implemented by same class.

99% confidence
A

ISP in Express means middleware should have focused responsibilities and minimal signatures - don't force middleware to handle unrelated concerns. Violation: Fat middleware handling auth, logging, validation, caching. function fatMiddleware(req, res, next, config: {auth: AuthConfig; logging: LogConfig; validation: ValidationConfig; cache: CacheConfig}) {...} - forces all concerns into one. ISP compliant: Separate middleware functions with focused signatures. const authMiddleware = (config: AuthConfig) => (req, res, next) => {...}; const loggingMiddleware = (config: LogConfig) => (req, res, next) => {...}; const validationMiddleware = (schema: ValidationSchema) => (req, res, next) => {...}. Usage: app.use(authMiddleware(authConfig)); app.use(loggingMiddleware(logConfig)); app.post('/api', validationMiddleware(schema), handler) - compose focused middleware. Benefits: Reusable middleware (auth reused across routes), testable in isolation, clear single responsibility. TypeScript pattern: Type middleware parameters specifically. interface AuthMiddleware {(req: Request, res: Response, next: NextFunction): void; config: AuthConfig} - explicit contract. Modern 2025: NestJS guards, interceptors, pipes are segregated middleware types - Guard (auth only), Interceptor (transformation only), Pipe (validation only). Best practice: One concern per middleware function, compose via app.use() chain. Avoid options objects with unrelated settings.

99% confidence
A

ISP in GraphQL means types should expose only fields relevant to client queries - avoid fat types forcing clients to ignore fields. Violation: type User {id: ID!; name: String!; email: String!; passwordHash: String!; internalUserId: Int!; databaseId: String!; createdAt: DateTime!; updatedAt: DateTime!; deletedAt: DateTime; lastLoginIp: String; sessionToken: String} - exposes internal/sensitive fields. ISP compliant: (1) Separate public and internal types. type PublicUser {id: ID!; name: String!; email: String!; createdAt: DateTime!}. type InternalUser {id: ID!; passwordHash: String!; internalUserId: Int!; sessionToken: String!} - different schemas for different clients. (2) Use interfaces for role-based views. interface UserBasic {id: ID!; name: String!} type PublicUser implements UserBasic {id: ID!; name: String!; email: String!} type AdminUser implements UserBasic {id: ID!; name: String!; email: String!; role: String!; permissions: [Permission!]!} - clients query interface, get appropriate fields. (3) Field-level authorization: Expose all fields in schema but @auth directive hides unauthorized fields. Modern 2025: GraphQL Federation - split schema into subgraphs by domain, each exposes focused interface. Benefits: Security (no sensitive field exposure), clarity (client sees only relevant fields), performance (fewer resolver calls). Best practice: Design schema from client perspective, not database schema. Use fragments for reusable field sets.

99% confidence
A

Plugin architecture naturally follows ISP - each plugin implements focused interface for specific capability, not monolithic interface. Pattern: Core defines capability-specific interfaces, plugins implement needed interfaces. Example: Text editor. Violation: interface Plugin {onLoad(), onSave(), onEdit(), onFormat(), onSearch(), onHighlight(), onAutocomplete()} - all plugins forced to implement all methods. ISP compliant: interface LoadPlugin {onLoad(): void} interface SavePlugin {onSave(content: string): void} interface FormatterPlugin {onFormat(text: string): string} interface HighlightPlugin {onHighlight(code: string): string}. Plugins: class MarkdownPlugin implements FormatterPlugin, HighlightPlugin {onFormat(text) {...} onHighlight(code) {...}} class AutosavePlugin implements SavePlugin {onSave(content) {...}} - each implements only relevant interfaces. Benefits: Plugins need only implement applicable capabilities, easy plugin development (minimal interface), core discovers plugins via interface checks: if('onFormat' in plugin) registerFormatter(plugin). Modern examples: VSCode extensions (LanguageProvider, CodeActionProvider, HoverProvider - separate interfaces), Webpack loaders (specific loader interface per file type), Babel plugins (visitor pattern with focused node handlers). TypeScript advantage: Structural typing enables implicit plugin detection without explicit implements. Best practice: Design plugin interfaces by capability (what plugin can do), not by lifecycle (when plugin runs).

99% confidence
A

ISP in repositories means separating read and write interfaces, query interfaces, and admin interfaces - clients depend only on needed operations. Violation: interface Repository {findById(id), findAll(), search(query), create(item), update(id, item), delete(id), bulkInsert(items), truncate(), migrate(), backup()} - mixes CRUD, admin, and migration concerns. ISP compliant patterns: (1) CQRS separation: interface QueryRepository {findById(id: string): Promise<T | null>; findAll(): Promise<T[]>; search(query): Promise<T[]>} interface CommandRepository {create(item: T): Promise; update(id: string, item: T): Promise; delete(id: string): Promise}. Read service depends on QueryRepository, write service depends on CommandRepository. (2) Role-specific interfaces: interface ReadOnlyRepository {findById(id), findAll()} interface ReadWriteRepository extends ReadOnlyRepository {create(item), update(id, item)} interface AdminRepository extends ReadWriteRepository {truncate(), migrate(), backup()}. Benefits: Testing easier (mock only needed interface), security (read-only access enforced at type level), clear contracts. Modern 2025: Prisma Client segregates by model (User.findMany vs Product.findMany), Drizzle ORM uses query builder pattern enabling focused interfaces. Best practice: Separate queries from commands, never expose admin operations to business logic layer.

99% confidence
A

Discriminated unions create type-safe focused interfaces via type narrowing - client handles only applicable cases without fat interface. Pattern: Union of focused types with discriminant property. Example: Violation with fat interface: interface Shape {type: 'circle' | 'square' | 'triangle'; radius?: number; side?: number; base?: number; height?: number; area(): number} - all shapes forced to have all properties (optional). ISP compliant with discriminated union: type Circle = {type: 'circle'; radius: number}; type Square = {type: 'square'; side: number}; type Triangle = {type: 'triangle'; base: number; height: number}; type Shape = Circle | Square | Triangle. Usage with type narrowing: function area(shape: Shape): number {switch(shape.type) {case 'circle': return Math.PI * shape.radius ** 2; case 'square': return shape.side ** 2; case 'triangle': return 0.5 * shape.base * shape.height;}} - each branch sees only relevant properties. Benefits: Type safety (no optional properties), exhaustiveness checking (TypeScript ensures all cases handled), focused types (Circle has only radius, no side/base/height). Modern 2025: Use with tagged templates for state machines. type State = {status: 'loading'} | {status: 'success'; data: Data} | {status: 'error'; error: Error} - each state has only applicable fields. Best practice: Prefer discriminated unions over optional properties for mutually exclusive data.

99% confidence
A

ISP in microservices means services expose focused APIs for specific client needs - avoid monolithic service contracts. Violation: Single UserService with endpoints: GET /users, POST /users, PUT /users/:id, DELETE /users/:id, GET /users/:id/orders, GET /users/:id/payments, POST /users/:id/preferences, GET /admin/users/audit - mixes user management, order queries, payment history, preferences, admin operations. ISP compliant: Separate services by bounded context: (1) UserManagementService: GET /users/:id, PUT /users/:id/profile (2) OrderService: GET /orders?userId=:id (3) PaymentService: GET /payments?userId=:id (4) PreferencesService: GET /users/:id/preferences, PUT /users/:id/preferences (5) AuditService (internal): GET /audit/users/:id. Benefits: Clients depend on specific service (mobile app uses UserManagement + OrderService, admin uses AuditService), independent scaling (scale OrderService during shopping season), clear service boundaries. API Gateway pattern: Gateway composes focused backend services, exposes client-specific APIs. GraphQL Federation: Each service exposes focused schema, gateway federates. Modern 2025: Backend for Frontend (BFF) pattern - separate gateway per client type (mobile BFF, web BFF, admin BFF), each exposes focused interface. Best practice: Design microservices around business capabilities (bounded contexts), not CRUD operations. Each service = focused interface.

99% confidence
A

Command pattern creates focused command interfaces - each command encapsulates single operation with minimal interface. Pattern: interface Command {execute(): void} - minimal interface. Concrete commands: class CreateUserCommand implements Command {constructor(private userData: UserData) {} execute() {/* create user logic /}} class SendEmailCommand implements Command {constructor(private email: Email) {} execute() {/ send email /}} class ProcessPaymentCommand implements Command {constructor(private payment: Payment) {} execute() {/ process payment */}}. Benefits: Invoker depends on Command interface (1 method), not specific command implementations. class CommandInvoker {execute(command: Command) {command.execute();}} - works with any command. ISP compliance: Clients depend only on execute(), don't see CreateUserCommand's userData or SendEmailCommand's email internals. Advanced: Command with undo. interface UndoableCommand extends Command {execute(): void; undo(): void} - still focused, two methods. Modern use cases: Redux actions (focused action creators), CQRS commands (each command class = single operation), Task queues (each task = command). TypeScript advantage: Discriminated union of commands: type AppCommand = CreateUserCommand | SendEmailCommand | ProcessPaymentCommand - type-safe command handling. Best practice: One operation per command class, invoker depends on minimal Command interface, rich command data encapsulated in constructor.

99% confidence
A

ISP makes testing easier - mock only methods client uses, not fat interfaces. Violation impact on testing: interface UserService {create(), update(), delete(), find(), list(), export(), import(), audit()} - 8 methods. Test needs only find(). Must mock all 8 methods even though test uses 1. const mockService = {create: jest.fn(), update: jest.fn(), delete: jest.fn(), find: jest.fn(), list: jest.fn(), export: jest.fn(), import: jest.fn(), audit: jest.fn()} - verbose. ISP improvement: interface UserFinder {find(id: string): Promise<User | null>} - test depends on focused interface. const mockFinder = {find: jest.fn().mockResolvedValue(user)} - mock only needed method. Benefits: (1) Less boilerplate - mock 1 method not 8. (2) Clear test intent - dependencies explicit. (3) Faster tests - fewer mocks to set up. (4) Type safety - TypeScript ensures mock matches interface. Modern 2025 tools: TypeScript jest.Mocked utility type creates typed mock. const mockFinder: jest.Mocked = {find: jest.fn()} - type-safe. Vitest, Jest auto-mock focused interfaces easily. Best practice: Design interfaces from test perspective - 'What does this component actually need?' Extract minimal interface for dependency injection. Pattern: Test-driven interface design - write test first, extract minimal interface from test dependencies.

99% confidence
A

API versioning enables ISP by allowing focused interfaces per version without breaking old clients. Strategy 1 - Version-specific interfaces: Keep v1 focused, add new interfaces in v2 instead of extending v1. Example: v1: interface UserAPI {getUser(id), updateUser(id, data)}. v2: interface UserAPI {getUser(id), updateUser(id, data)} + new interface UserPreferencesAPI {getPreferences(id), setPreferences(id, prefs)} - separate interface for new capability. Don't: v2: interface UserAPI {getUser(id), updateUser(id, data), getPreferences(id), setPreferences(id, prefs)} - fat interface. Strategy 2 - Additive changes only: Add new optional methods/properties, don't modify existing. GraphQL approach: Add fields with @deprecated, new fields without breaking clients. type User {id: ID!; name: String; email: String @added(version: '2.0')} - v1 clients ignore email. Strategy 3 - Interface splitting on version boundaries: When introducing v3, refactor v2 fat interface to focused v3 interfaces. Legacy v2 adapter wraps focused v3 interfaces. Benefits: Old clients use focused v1 interfaces, new clients use focused v2 interfaces, migration gradual. Modern 2025: Stripe API versioning (2019-12-03, 2020-08-27) adds capabilities without breaking changes. tRPC uses TypeScript for version-safe clients. Best practice: New version = opportunity to apply ISP, use adapters to maintain backward compatibility.

99% confidence