Interface Segregation Principle (ISP)
“Clients should not be forced to depend on interfaces they do not use.” — Robert C. Martin
The Interface Segregation Principle states that it’s better to have many small, specific interfaces than one large, general-purpose interface. Classes should only need to implement methods they actually use.
Understanding ISP
A “fat interface” forces implementers to either:
- Implement methods they don’t need
- Throw
NotImplementedException(violating LSP) - Leave methods empty (misleading)
Small, focused interfaces let classes implement only what makes sense for them.
Violation Example: Fat Interface
// BAD: One interface trying to do everything
public interface IWorker
{
void Work();
void TakeBreak();
void Eat();
void Sleep();
void Manage();
void Code();
void Design();
void Test();
void Deploy();
}
// Developer must implement everything
public class Developer : IWorker
{
public void Work() => Console.WriteLine("Coding...");
public void TakeBreak() => Console.WriteLine("Break time");
public void Eat() => Console.WriteLine("Eating lunch");
public void Sleep() => Console.WriteLine("Sleeping");
// Developer doesn't manage!
public void Manage() => throw new NotImplementedException();
public void Code() => Console.WriteLine("Writing code");
public void Design() => throw new NotImplementedException(); // Maybe...
public void Test() => Console.WriteLine("Writing tests");
public void Deploy() => Console.WriteLine("Deploying to production");
}
// Manager must also implement everything
public class Manager : IWorker
{
public void Work() => Console.WriteLine("Managing team");
public void TakeBreak() => Console.WriteLine("Break time");
public void Eat() => Console.WriteLine("Eating lunch");
public void Sleep() => Console.WriteLine("Sleeping");
public void Manage() => Console.WriteLine("Managing projects");
// Manager doesn't code!
public void Code() => throw new NotImplementedException();
public void Design() => throw new NotImplementedException();
public void Test() => throw new NotImplementedException();
public void Deploy() => throw new NotImplementedException();
}
Problems:
- Classes implement methods they don’t use
NotImplementedExceptioneverywhere- Changes to interface affect all implementers
- Hard to understand what each class actually does
Applying ISP: Segregated Interfaces
// GOOD: Small, focused interfaces
public interface IWorker
{
void Work();
}
public interface IHuman
{
void Eat();
void Sleep();
void TakeBreak();
}
public interface IManager
{
void Manage();
void ConductMeeting();
void ReviewPerformance();
}
public interface IDeveloper
{
void Code();
void Review();
void Debug();
}
public interface IDesigner
{
void Design();
void CreateMockup();
}
public interface ITester
{
void Test();
void WriteTestCases();
}
public interface IDevOps
{
void Deploy();
void Monitor();
void Configure();
}
// Developer implements only what makes sense
public class Developer : IWorker, IHuman, IDeveloper
{
public void Work() => Code();
public void Code() => Console.WriteLine("Writing clean code");
public void Review() => Console.WriteLine("Reviewing pull request");
public void Debug() => Console.WriteLine("Hunting bugs");
public void Eat() => Console.WriteLine("Lunch break");
public void Sleep() => Console.WriteLine("Getting rest");
public void TakeBreak() => Console.WriteLine("Coffee time");
}
// Manager implements management capabilities
public class Manager : IWorker, IHuman, IManager
{
public void Work() => Manage();
public void Manage() => Console.WriteLine("Managing team");
public void ConductMeeting() => Console.WriteLine("Running standup");
public void ReviewPerformance() => Console.WriteLine("Quarterly review");
public void Eat() => Console.WriteLine("Lunch meeting");
public void Sleep() => Console.WriteLine("Getting rest");
public void TakeBreak() => Console.WriteLine("Checking emails");
}
// Tech Lead combines multiple roles
public class TechLead : IWorker, IHuman, IDeveloper, IManager
{
public void Work() => Console.WriteLine("Balancing tasks");
public void Code() => Console.WriteLine("Writing critical code");
public void Review() => Console.WriteLine("Reviewing architecture");
public void Debug() => Console.WriteLine("Solving complex issues");
public void Manage() => Console.WriteLine("Leading the team");
public void ConductMeeting() => Console.WriteLine("Technical discussion");
public void ReviewPerformance() => Console.WriteLine("Team review");
public void Eat() => Console.WriteLine("Lunch");
public void Sleep() => Console.WriteLine("Sleep");
public void TakeBreak() => Console.WriteLine("Break");
}
// Full-stack developer with DevOps skills
public class FullStackDevOps : IWorker, IHuman, IDeveloper, IDevOps
{
public void Work() => Console.WriteLine("Full stack work");
public void Code() => Console.WriteLine("Frontend and backend");
public void Review() => Console.WriteLine("Code review");
public void Debug() => Console.WriteLine("Debugging");
public void Deploy() => Console.WriteLine("CI/CD pipeline");
public void Monitor() => Console.WriteLine("Checking metrics");
public void Configure() => Console.WriteLine("Infrastructure as code");
public void Eat() => Console.WriteLine("Eating");
public void Sleep() => Console.WriteLine("Sleeping");
public void TakeBreak() => Console.WriteLine("Break");
}
Real-World Example: Repository Interfaces
// BAD: Fat repository interface
public interface IRepository<T>
{
T GetById(int id);
List<T> GetAll();
List<T> GetPaged(int page, int size);
T GetByCode(string code);
void Add(T entity);
void Update(T entity);
void Delete(int id);
void SoftDelete(int id);
void Restore(int id);
void BulkInsert(List<T> entities);
void BulkUpdate(List<T> entities);
int Count();
bool Exists(int id);
}
// Not all entities support all operations!
// Products can be soft-deleted, Orders cannot
// Users have codes, Products don't
// GOOD: Segregated repository interfaces
public interface IReadRepository<T>
{
Task<T> GetByIdAsync(int id);
Task<List<T>> GetAllAsync();
Task<bool> ExistsAsync(int id);
Task<int> CountAsync();
}
public interface IWriteRepository<T>
{
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
public interface IPagedRepository<T>
{
Task<PagedResult<T>> GetPagedAsync(int page, int pageSize);
}
public interface ISoftDeletable<T>
{
Task SoftDeleteAsync(int id);
Task RestoreAsync(int id);
Task<List<T>> GetDeletedAsync();
}
public interface IBulkOperations<T>
{
Task BulkInsertAsync(List<T> entities);
Task BulkUpdateAsync(List<T> entities);
}
public interface ICodeLookup<T>
{
Task<T> GetByCodeAsync(string code);
}
// Product repository - supports soft delete and bulk operations
public class ProductRepository :
IReadRepository<Product>,
IWriteRepository<Product>,
IPagedRepository<Product>,
ISoftDeletable<Product>,
IBulkOperations<Product>
{
// Implements all relevant interfaces
}
// Order repository - no soft delete, no bulk
public class OrderRepository :
IReadRepository<Order>,
IWriteRepository<Order>,
IPagedRepository<Order>
{
// Only implements what Orders need
}
// User repository - has code lookup
public class UserRepository :
IReadRepository<User>,
IWriteRepository<User>,
ICodeLookup<User>
{
// Users can be looked up by employee code
}
ISP in Service Design
// BAD: God service interface
public interface IUserService
{
User GetById(int id);
User GetByEmail(string email);
void Create(User user);
void Update(User user);
void Delete(int id);
void ChangePassword(int userId, string newPassword);
void ResetPassword(string email);
void SendVerificationEmail(int userId);
void VerifyEmail(string token);
void Enable2FA(int userId);
void Disable2FA(int userId);
void UpdateProfile(int userId, ProfileData data);
void UploadAvatar(int userId, byte[] image);
List<User> Search(string query);
PagedResult<User> GetPaged(int page, int size);
}
// GOOD: Segregated services
public interface IUserQueryService
{
Task<User> GetByIdAsync(int id);
Task<User> GetByEmailAsync(string email);
Task<List<User>> SearchAsync(string query);
Task<PagedResult<User>> GetPagedAsync(int page, int size);
}
public interface IUserCommandService
{
Task CreateAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}
public interface IPasswordService
{
Task ChangePasswordAsync(int userId, string currentPassword, string newPassword);
Task ResetPasswordAsync(string email);
}
public interface IEmailVerificationService
{
Task SendVerificationEmailAsync(int userId);
Task VerifyEmailAsync(string token);
}
public interface ITwoFactorAuthService
{
Task EnableAsync(int userId);
Task DisableAsync(int userId);
Task<bool> ValidateCodeAsync(int userId, string code);
}
public interface IProfileService
{
Task UpdateProfileAsync(int userId, ProfileData data);
Task UploadAvatarAsync(int userId, byte[] image);
Task<ProfileData> GetProfileAsync(int userId);
}
Benefits of ISP
| Benefit | Description |
|---|---|
| Smaller Contracts | Interfaces are easier to understand |
| Flexible Implementation | Classes implement only what they need |
| Easier Testing | Mock only relevant interfaces |
| Better Cohesion | Related methods are grouped together |
| Reduced Coupling | Clients depend on minimal interface |
Interview Tips
Common Questions:
- “Explain ISP with an example”
- “How do you decide when to split an interface?”
- “What’s the relationship between ISP and SRP?”
Key Points:
- Fat interfaces force unnecessary dependencies
- Small interfaces are easier to implement and test
- ISP relates to SRP - both about focused responsibilities
- Use role interfaces instead of header interfaces
Signs of ISP Violation:
NotImplementedExceptionin interface implementations- Empty method implementations
- Interfaces with 10+ methods
- Clients only using a few methods of an interface
Interface Size Guidelines:
- 1-5 methods: Good
- 5-10 methods: Consider splitting
- 10+ methods: Probably needs splitting