Skip to main content

Sign in to CleanKata

Track your progress, earn XP, and unlock every lesson.

By signing in you agree to our Terms of Use and Privacy Policy.

Clean Architecture70 XP7 min

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