SOLID

Single Responsibility Principle

A class should have one, and only one, reason to change — it should be responsible to a single actor or concern.

One job per class.

The Single Responsibility Principle (SRP) says each class should do one thing. If a class handles user data and formats reports and saves to a database, it has three jobs — and three reasons to break when any of them changes.

Split those into a User, a ReportFormatter, and a UserRepository. Now each is simpler, easier to test, and changes to reporting don't risk breaking persistence.

bad.py
# Problem: Employee has many reasons to change (HR, payroll, IT).
class Employee:
    def __init__(self, name, hours, rate):
        self.name, self.hours, self.rate = name, hours, rate

    def calculate_pay(self):      # changes when payroll rules change
        return self.hours * self.rate

    def report_hours(self):       # changes when HR reporting changes
        return f"{self.name}: {self.hours}h"

    def save(self):               # changes when DB schema changes
        db.execute("INSERT ...", self.__dict__)
good.py
# Fix: one class ≈ one actor / one reason to change.
class Employee:
    def __init__(self, name, hours, rate):
        self.name, self.hours, self.rate = name, hours, rate

class PayCalculator:
    def pay(self, employee):
        return employee.hours * employee.rate

class HourReporter:
    def report(self, employee):
        return f"{employee.name}: {employee.hours}h"

class EmployeeRepository:
    def save(self, employee):
        db.execute("INSERT ...", employee.__dict__)
# Payroll can change without touching reporting or persistence.

"Reason to change" = an actor.

Martin's precise phrasing: "A module should be responsible to one, and only one, actor." An actor is a group of stakeholders who want changes for the same reason (e.g. accounting, operations, DBAs). If two actors' needs live in one class, changing for one can break the other.

Symptoms of violation A class named with "And", giant classes, methods touching unrelated concerns, and merge conflicts because many teams edit the same file.

Granularity and cohesion.

  • Cohesion connection — SRP is high cohesion applied at the class level.
  • Too-fine SRP — over-splitting creates many trivial classes and scattered logic; balance is needed.
  • Responsibility ≠ method count — a class can have many methods if they serve one responsibility.
  • Architectural SRP — the same idea scales to modules and services (bounded contexts).

Interview Questions

Filter

Single Responsibility Principle: a class (or module) should have only one reason to change — that is, one responsibility or job.

The Single Responsibility Principle — the first of the five SOLID principles of object-oriented design.

It makes classes easier to understand, test, and change; limits the ripple effect of changes; reduces the chance one change breaks unrelated behavior; and improves reuse because focused classes can be used independently.

A User class that stores user data, validates input, formats HTML for display, and writes to the database. It has multiple reasons to change (data model, validation rules, UI, persistence) and should be split.

A distinct source of change requests — typically a stakeholder/actor or concern. If business rules, UI, and persistence would each cause edits to the same class, that's three reasons to change, violating SRP.

No — the idea applies at every level: functions/methods should do one thing, classes should have one responsibility, and modules/services should own one cohesive concern. It's a general design principle.

A class or method name containing "And" (e.g. ValidateAndSave), a very large class, or difficulty describing what the class does in one short sentence without "and."

SRP is essentially high cohesion at the class level. A class with a single responsibility has members that all work toward that one purpose (functional cohesion). Violating SRP produces low-cohesion classes mixing unrelated concerns. So "one responsibility" and "highly cohesive" describe the same desirable property from different angles.

His refined definition ties a "reason to change" to an actor — a group of people (a role/department) who request changes for the same reason. A module should serve exactly one actor, so changes demanded by one stakeholder group don't force edits that affect another. Example: if an accounting rule and an HR report both live in one class, satisfying accounting could break HR's expectations — two actors, violating SRP.

Yes. SRP is about a single responsibility/reason to change, not a method count. A class can legitimately have many methods if they all serve one cohesive purpose (e.g. a String class). Conversely, a class with two methods can violate SRP if those methods belong to different concerns. Judge by responsibility and reasons to change, not size alone.

Identify the distinct responsibilities (often grouped by which actor/concern drives change), then Extract Class: move each responsibility's data and behavior into its own focused class. Have the original class delegate to the extracted collaborators (composition), injecting them where useful. Do it incrementally behind tests. E.g. split a bloated Invoice into Invoice (data/business rules), InvoicePrinter (formatting), and InvoiceRepository (persistence).

Applied blindly it can, producing fragmentation where logic is scattered across many trivial classes, hurting readability. The goal isn't maximum splitting but grouping by responsibility/actor: things that change together stay together, things that change for different reasons are separated. Over-application trades one problem (a god class) for another (shotgun surgery across many classes). Balance using cohesion and real reasons-to-change as the guide.

A class with one responsibility has a small, focused surface and fewer dependencies, so it's easy to test in isolation with clear inputs/outputs and minimal mocking. A multi-responsibility class needs many setup paths and dependencies (DB, UI, network) to exercise any one behavior, making tests complex and brittle. Splitting responsibilities lets you unit-test each concern independently and mock collaborators at clean seams.

SRP is foundational: cohesive, single-purpose classes make the others easier. Small responsibilities create natural extension points (OCP), well-defined behaviors that subtypes can honor (LSP), focused interfaces (ISP), and clean seams to depend on abstractions (DIP). Conversely, a god class violating SRP tends to violate the rest too. SRP shapes the units the other principles operate on.

Shotgun surgery is a code smell where one logical change forces edits in many places. It's the flip side of an SRP problem: often a single responsibility has been scattered across many classes (the opposite of a god class). SRP done well keeps a responsibility in one place so a change touches one class; both a bloated class (too many responsibilities) and scattered responsibility (shotgun surgery) are failures to align class boundaries with reasons to change.

Use "reasons to change / actors" and cohesion as the lens rather than arbitrary size. Group things that change together and for the same stakeholder; separate things that change for different reasons or at different rates. Consider the axes of anticipated change in the domain — if formatting and persistence historically change independently, they're separate responsibilities. Avoid both extremes: a class touching multiple actors (too coarse) and responsibilities smeared across many anemic classes (too fine). The Common Closure Principle at the module level echoes this: classes that change together belong together. Ultimately it's judgment informed by how the system actually evolves.

Martin's Employee example: it has calculatePay() (owned by accounting), reportHours() (owned by HR/operations), and save() (owned by DBAs). If both pay and hours calculations share a helper method and accounting changes it, HR's report silently breaks — because two actors depend on one module. Consequences: unexpected regressions, merge conflicts when different teams edit the same class, and fear of change. The fix separates the actor-specific logic (e.g. via separate classes coordinated by a facade), so each actor's changes are isolated.

Sometimes. Aggressively splitting responsibilities can increase the number of collaborators an object must talk to, which can bump into Law of Demeter concerns and add coordination code. It can also fragment a naturally cohesive workflow so the "story" of a use case is spread thin. The resolution is to split along genuine reasons-to-change/actor boundaries (which usually keeps interactions clean), and use coordinating objects (services/facades) to reassemble a workflow from focused parts. When splitting starts increasing coupling or obscuring the flow, the boundary is probably wrong — SRP should reduce, not increase, overall complexity.

At the architecture level, SRP becomes "a service/module owns one cohesive business capability (bounded context) and its data," changing for one business reason. This mirrors class-level SRP: a service serving multiple unrelated capabilities becomes a distributed god service, hard to deploy and scale independently and coupling multiple teams. Related module-level principles (Common Closure Principle: classes that change together are packaged together; Single-actor ownership) apply the same logic. Getting service boundaries right (aligned to reasons-to-change/teams) is essentially SRP applied to system decomposition, and getting it wrong yields either a monolith or a chatty, tightly-coupled "distributed monolith."

Don't split responsibilities you can only imagine; split the ones the code actually demonstrates changing for different reasons. Early on, a slightly larger class is fine; as distinct reasons-to-change reveal themselves (e.g. formatting keeps changing separately from business rules, or a second actor appears), extract then. This aligns SRP with YAGNI and the rule of three: refactor toward separated responsibilities in response to real pressure rather than speculation. The cost of an ill-judged early split (fragmentation, indirection) can exceed the cost of a later, well-informed extraction, so let observed change guide granularity.

Combine signals: metrics (large classes by LOC, high LCOM indicating unrelated method clusters, many injected dependencies), version-control analysis (files with high churn edited by many different teams/for many reasons are prime suspects — change-coupling analysis reveals responsibilities that should be together or apart), naming smells ("Manager/Util/AndX"), and difficulty writing focused unit tests. Then prioritize by risk/churn and refactor incrementally with tests, extracting responsibilities that historically changed independently. Tools like code-ownership and co-change heatmaps make the actor/reason-to-change boundaries visible from real history rather than guesswork.

"Responsibility" is subjective, so anchor it concretely: (1) use the actor definition — one module per stakeholder group that requests changes; (2) use reasons to change derived from real history (co-change analysis) rather than intuition; (3) use cohesion metrics as corroboration; (4) apply the "describe it in one sentence without 'and'" test. Frame decisions around how the system actually evolves and who owns what, making SRP a tool for isolating change and ownership rather than an abstract mandate. This turns a fuzzy principle into concrete, defensible boundary decisions, while accepting that some judgment is irreducible.

Roughly, but "one thing" is better understood as "one responsibility / one reason to change," not "one method" or "as little as possible." A class can do quite a bit as long as it all serves a single cohesive purpose.

Dependency Inversion / composition: after Extract Class, the original class holds references to the extracted collaborators (ideally via abstractions) and delegates to them, keeping responsibilities separated but coordinated.

Vague "catch-all" names like Manager, Helper, Utils, Processor, or anything with "And" (e.g. ParseAndValidate) often signal a class doing more than one thing.

No. It can expose several public methods as long as they all pertain to the same responsibility. For example, a Stack with push, pop, and peek has one responsibility (LIFO storage) despite multiple methods. SRP counts reasons to change, not methods.

When each class serves one actor/responsibility, different teams working on different concerns edit different files. A god class touched by many teams for many reasons becomes a hotspot where everyone's changes collide. Splitting by responsibility localizes changes, so independent concerns evolve in separate files and conflicts drop.

Extract Class: identify a cluster of fields/methods that form a distinct responsibility and move them into a new class, leaving the original to delegate. Related moves include Extract Method (for functions doing too much) and Move Method (relocating behavior to the class it truly belongs to). Done behind tests, these incrementally converge a bloated class toward single responsibilities.

Once a responsibility is split into several focused classes, clients could be burdened with orchestrating them all. A Facade provides a single, simplified entry point that coordinates the collaborators for a common use case, restoring an easy-to-use surface without merging the responsibilities back together. This resolves the tension between fine-grained SRP classes and a coherent workflow: the small classes keep single responsibilities, while the facade owns the (separate) responsibility of coordinating them for a particular client scenario.

The Common Closure Principle (CCP) is essentially SRP for packages/components: "classes that change together, and for the same reasons, should be packaged together; classes that change for different reasons should be separated." Both align module boundaries with reasons-to-change so a single change is localized. SRP governs the class, CCP governs the component; together they ensure that a change driven by one actor touches one class inside one component, minimizing the blast radius. Getting them consistent (a component whose classes share responsibilities, whose classes each have one responsibility) yields a codebase where change is predictable and contained.