OOP Concepts · Design Principles

Interface Segregation Principle

The I in SOLID: no class should be forced to implement methods it does not use. Split big interfaces into small ones that each describe one role.

Intermediate 5 min Lesson 26 of 38 Runnable examplesExamples use modern JavaScript (ES2022 private fields) and C# 13.
0/38 in OOP Concepts
On this page · 5 min · Intermediate

In simple words#

A remote with 80 buttons when you only need 5 is annoying. Prefer several small, focused interfaces over one giant one.

ISP
Don’t force classes to implement unused methods.
Fat interface
Too many unrelated members.
Role interfaces
Small interfaces per capability.

Learning objectives#

  • State the Interface Segregation Principle
  • Spot fat interfaces
  • Split an interface into role interfaces
  • Combine small interfaces in one class

Explanation#

A fat interface such as IMachine { Print(); Scan(); Fax(); } forces a basic printer to implement Scan and Fax — usually by throwing “not supported”.

Those fake implementations are traps: callers think every IMachine can scan, and find out at runtime that it cannot (an LSP violation too).

ISP says: split by role. IPrinter { Print(); }, IScanner { Scan(); }, IFax { Fax(); }. A class implements only the roles it really has.

A multi-function office machine simply implements several: class OfficeMachine : IPrinter, IScanner.

Clients depend only on what they use. A PrintQueue needs IPrinter, nothing more — so it works with every printer.

Syntax#

C# Illustrative
// ✗ Fat interface
interface IMachine { void Print(string doc); void Scan(string doc); void Fax(string doc); }


// ✓ Role interfaces
interface IPrinter { void Print(string doc); }
interface IScanner { void Scan(string doc); }


class BasicPrinter : IPrinter { public void Print(string doc) { } }
class OfficeMachine : IPrinter, IScanner
{
    public void Print(string doc) { }
    public void Scan(string doc) { }
}

Code example: Printers that do not pretend to scan#

JavaScriptPrinters that do not pretend to scan Runnable
// ✗ Fat contract: every device must print, scan and fax
class OldPrinter {
  print(doc) { return `Printing ${doc}`; }
  scan() { throw new Error('Not supported'); }
  fax() { throw new Error('Not supported'); }
}


// ✓ Small role-based capabilities
const canPrint = d => typeof d.print === 'function';
const canScan = d => typeof d.scan === 'function';


class BasicPrinter {
  print(doc) { return `Printing ${doc}`; }
}


class OfficeMachine {
  print(doc) { return `Printing ${doc}`; }
  scan(doc) { return `Scanning ${doc}`; }
}


const devices = [new BasicPrinter(), new OfficeMachine()];
console.log(devices.filter(canScan).map(d => d.scan('passport.pdf')));
console.log(devices.filter(canPrint).length);


try { new OldPrinter().scan('x'); } catch (e) { console.log('OldPrinter:', e.message); }
Expected output
[ 'Scanning passport.pdf' ]
2
OldPrinter: Not supported
Opens this example in the full playground

Step-by-step explanation#

  1. OldPrinter claims to scan and fax but throws — a promise it cannot keep.
  2. BasicPrinter implements only printing; OfficeMachine implements printing and scanning.
  3. Code that needs scanning asks for the scanning capability and gets only devices that really can.
  4. Both devices still count as printers, so the print queue works with either.

Real-world use case#

A data layer splits IReadRepository<T> (Get, List) from IWriteRepository<T> (Add, Update, Delete). Reporting services depend only on the read interface and cannot accidentally modify data.

Common mistakes#

One interface per class, copied from its methods

Mirroring every public method of a class in a single interface recreates the fat-interface problem.

Splitting too finely

Ten one-method interfaces that are always used together add noise. Group by what clients actually need.

Best practices#

  • Design interfaces from the client’s point of view
  • Name interfaces after roles (IPrinter, IScanner)
  • Let one class implement several small interfaces
  • Watch for implementations that throw NotSupportedException

Remember this#

Many small interfaces > one fat interface.

Never force a class to implement what it cannot do.

Clients depend only on what they use.

Practice exercise#

Your task: A Worker contract has work() and eat(). A Robot can work but not eat. Split it into Workable and Eatable roles, implement Human (both) and Robot (work only), and run a shift that calls work() on everyone who can work.

Need a hint?
Filter with typeof x.work === "function".
Show solution
JavaScript Runnable
class Human { work() { return 'human working'; } eat() { return 'human eating'; } }
class Robot { work() { return 'robot working'; } }
const staff = [new Human(), new Robot()];
console.log(staff.filter(s => typeof s.work === 'function').map(s => s.work()));
// [ 'human working', 'robot working' ]
console.log(staff.filter(s => typeof s.eat === 'function').length); // 1
Opens this example in the full playground

Mini quiz#

Interface Segregation Principle quizMultiple choice Question 1 of 3

What does ISP recommend?

Score so far: 0/0

Want more? Practise all OOP Concepts MCQs · OOP Concepts interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.