SRP: The Single Responsibility Principle
A module should be responsible to only one actor — preventing changes for one department from accidentally breaking another.
Why this matters
SRP is commonly misread as "a module should do only one thing." The real definition, as Robert Martin clarifies in Clean Architecture, is more precise: a module should be responsible to one, and only one, actor. An actor is a group of people — a department, a role — whose requirements drive changes to that module.
The canonical example is an Employee class with three methods: calculatePay() requested by the CFO, reportHours() requested by the COO, and save() requested by the CTO. When these three methods share a private helper — say, _regularHours() — the CFO's team can accidentally break the COO's reports simply by adjusting the pay calculation algorithm. The two actors are now unknowingly coupled through a shared class. Cohesion is the force that keeps things that change for the same reason together; SRP is the principle that separates things that change for different reasons.
✗The problem
Three actors — CFO, COO, CTO — all depend on one class. A change requested by one actor silently breaks the logic used by another.
Bad
class Employee:
def calculate_pay(self) -> float: # owned by CFO
hours = self._regular_hours()
return hours * self.hourly_rate
def report_hours(self) -> float: # owned by COO
hours = self._regular_hours() # reuses same helper — coupling!
return hours
def save(self) -> None: # owned by CTO / DBA
db.save(self)
def _regular_hours(self) -> float: # shared — dangerous
return self.total_hours - self.overtime_hours
class Employee {
calculatePay(): number { // CFO's concern
return this.regularHours() * this.hourlyRate;
}
reportHours(): number { // COO's concern
return this.regularHours(); // accidental coupling!
}
save(): void { // CTO's concern
db.save(this);
}
private regularHours(): number { // shared — fragile
return this.totalHours - this.overtimeHours;
}
}
✓The solution
Three separate classes, one actor each. The Employee data structure holds state only — it has no behavior. Each class owns its own helper logic.
Good
class Employee:
"""Data structure only — no behavior."""
total_hours: float
overtime_hours: float
hourly_rate: float
class PayCalculator: # answers to CFO
def calculate_pay(self, emp: Employee) -> float:
return self._regular_hours(emp) * emp.hourly_rate
def _regular_hours(self, emp: Employee) -> float:
return emp.total_hours - emp.overtime_hours
class HourReporter: # answers to COO
def report_hours(self, emp: Employee) -> float:
return emp.total_hours # uses its own definition
class EmployeeRepository: # answers to CTO / DBA
def save(self, emp: Employee) -> None:
db.save(emp)
interface Employee {
totalHours: number;
overtimeHours: number;
hourlyRate: number;
}
class PayCalculator { // answers to CFO
calculatePay(emp: Employee): number {
return this.regularHours(emp) * emp.hourlyRate;
}
private regularHours(emp: Employee): number {
return emp.totalHours - emp.overtimeHours;
}
}
class HourReporter { // answers to COO
reportHours(emp: Employee): number {
return emp.totalHours; // owns its own definition
}
}
class EmployeeRepository { // answers to CTO
save(emp: Employee): void {
db.save(emp);
}
}
💡Key takeaway
SRP is not about doing one thing — it's about serving one actor. If two different executives can request conflicting changes to the same module, that module is violating SRP and is a ticking time bomb for accidental coupling.
🔧 Some exercises may still have errors. If something seems wrong, use the Feedback button (bottom-right of the page) to report it — it helps us fix it fast.
Hint: If different executives can request conflicting changes to the same class, it serves multiple actors — split it.
✗ Your version