Beginner16 min readSOLID Principleslive prototype

Single Responsibility Principle (SRP)

The "S" in SOLID: a class should have only one reason to change — that is, it should answer to just one person or part of the business. Mix two jobs in one class and a change to either one can break the other.

The idea

What it is

The Single Responsibility Principle says: a class should do one job, and have one reason to change. It is the S in SOLID, the first of five rules for keeping object-oriented code easy to change. The idea is small but powerful — when a class only does one thing, you always know where to look, and a change in one place can't accidentally break something unrelated.

Picture a restaurant where one person is the chef and the accountant and the dishwasher. When the menu changes, the tax rules change, and the dishwasher breaks, the same overloaded person has to deal with all three — and a mistake while doing the books might leave the kitchen unattended. A well-run restaurant gives each job to a dedicated person. SRP asks you to design classes the same way: one clear job each, so the people who care about that job have exactly one place to go.

The one sentence to remember

A class should have only one reason to change — meaning it should be answerable to one actor (one person or part of the business). If you can describe what a class does without using the word "and", you're probably following SRP.

Mechanics

How it works

"One reason to change" really means "one actor"

Beginners first hear SRP as “a class should do one thing,” which is fuzzy — how big is one thing? Robert C. Martin (Uncle Bob), who named the principle, later sharpened it: a module should be responsible to one, and only one, actor. An actor is the group of people who would ask for a change — a stakeholder, a department, a role. The real question isn't “how many things does this class do?” but “how many different people would ever ask me to change it?” If the answer is more than one, the class is doing too much.

Why does the actor matter more than the count of methods? Because changes come from people. If Accounting changes how pay is calculated and the DBA changes how data is stored, those two requests should never collide inside the same class. When they do, fixing one risks breaking the other — and that's exactly the bug SRP is designed to prevent.

Employee god class · 3 reasons to change + calculatePay() + saveToDatabase() + generateReport() answers to 3 different bosses split PayCalculator + calculatePay() 1 reason Accounting EmployeeRepository + saveToDatabase() 1 reason DBA ReportFormatter + generateReport() 1 reason Reporting
Left: one Employee class crams together pay math, database saves, and report formatting — three reasons to change, three different bosses. Right: split into PayCalculator, EmployeeRepository, and ReportFormatter, each owned by one actor with one reason to change. The behaviour is identical; the responsibility is now untangled.

The classic smell: business logic + persistence + formatting

The textbook SRP violation is a single class that computes something, saves it to a database, and formats it for a report. Those are three different concerns answering to three different actors — the business rules belong to a domain expert, the storage belongs to a DBA, and the report layout belongs to whoever reads the report. Stuffed into one class, every one of those people is now touching the same file, and a change for any of them can ripple into the others.

The danger isn't ugliness — it's accidental breakage

Imagine Employee.calculatePay() and Employee.generateReport() happen to share a private helper that rounds money. Accounting asks for a rounding change, you tweak the helper, and the report — owned by a totally different team — silently changes too. Two actors collided in one class. SRP keeps them in separate classes so their changes can't reach each other.

How to spot a violation

  • The "and" test — if you can't describe the class without "and" (“it calculates pay and saves to the DB and prints reports”), it has more than one responsibility.
  • Methods that change for different reasons — list who would request a change to each method. If two methods would be changed by two different roles, those methods belong in two different classes.
  • Mixed vocabulary — one class talking about money rules, SQL tables, and HTML layout is juggling three worlds at once.
  • A name that's a grab-bag — classes called Manager, Helper, Util, or Processor often hide several unrelated jobs behind a vague label.

How to fix it: extract one class per responsibility

The fix is Extract Class: pull each responsibility into its own focused class, then have the original object use them (often by holding references to them or receiving them via its constructor). The god class either disappears or shrinks into a thin coordinator that simply delegates. You don't lose any behaviour — you just give each job an owner. After the split, the “who would change this?” question has exactly one answer per class.

SRP ≠ "one method per class"

SRP is about reasons to change, not method count. A class with ten methods that all serve one actor is perfectly fine — that's still one responsibility. The goal is cohesion (everything in the class belongs together), not the smallest possible class.

Interactive prototype

See it. Build it. Break it.

A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.

About this simulation

A hands-on sorter. You start with a god class called Employee holding six methods that secretly belong to three different bosses — Accounting, Database, and Reporting. Click a method, then click the bucket you think owns it; a single fixed note tells you whether it fits and which boss would ask to change it. A live reasons to change counter shows the god class stuck at 3. Sort all six correctly and the prototype reveals the refactor: three focused classes, each with the counter dropped to 1. Only one note ever shows, so nothing scrolls or grows the page.

Hands-on

Try these yourself

Open the prototype above, predict what happens, then verify.

try 01

Read the god class out loud

In the prototype, look at the Employee god class before clicking anything. Read its six methods and try the "and" test out loud: “it calculates pay AND saves to the database AND formats reports…”. Notice the reasons to change counter pinned at 3 — that number is the whole problem in one digit.

try 02

Sort each method to its owner

Click a method, then click the bucket — Accounting, Database, or Reporting — you think owns it. The fixed note tells you whether it fits and, crucially, which boss would ask to change it. Get one wrong on purpose (drop saveToDatabase into Accounting) and read why an Accountant would never request that change. Then move it to the right place.

try 03

Reveal the refactor and watch the counter drop

Once all six methods are sorted into the correct actor, the prototype reveals the refactored design: three focused classes, each with its reasons to change counter dropped from 3 to 1. Compare the before and after — same behaviour, but now each class answers to exactly one boss. That drop from 3→1 is SRP.

In practice

When to use it — and what trips people up

When to apply SRP

Reach for SRP whenever a class starts answering to more than one actor: a model that also talks to the database, a service that also formats output, a controller that also holds business rules. It's especially valuable on code that changes often — the more a class is edited, the more painful it is when two unrelated jobs live inside it. In interviews, splitting a god class into focused classes is one of the fastest ways to show clean design instinct.

Don't over-split into a swarm of tiny classes

SRP can be taken too far. If you create a separate class for every method, you trade one big tangle for a maze of one-line classes that are hard to follow — premature decomposition. Split when responsibilities answer to different actors or change for different reasons, not just to make classes smaller. A cohesive class with several related methods is good design, not a violation.

What it gives you

  • Changes stay local — a request from one actor touches one class, so you can't accidentally break an unrelated feature.
  • Easier to read and reason about: each class has a clear, nameable job, so you always know where to look.
  • Simpler to test — a focused class has fewer dependencies and one purpose, so its unit tests are small and targeted.
  • Encourages reuse: an extracted ReportFormatter or Repository can be used by other classes instead of being trapped inside one.

Common mistakes

  • More classes and files to navigate — the design has more moving parts, which can feel heavier for very small programs.
  • Easy to over-apply: splitting too aggressively produces a swarm of tiny one-method classes that obscure the flow.
  • "Responsibility" is judgement-based — reasonable engineers disagree on where one actor ends and another begins.
  • Adds indirection: behaviour that was in one place is now spread across collaborators, so you may jump between files to trace a flow.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

// ───── BEFORE: a god class with three reasons to change ─────
// Accounting owns the pay math, the DBA owns persistence,
// and Reporting owns the report layout — all crammed into one class.
class Employee {
  constructor(public name: string, public hours: number, public rate: number) {}

  calculatePay(): number {            // ← Accounting would change this
    return this.hours * this.rate;
  }
  saveToDatabase(): void {            // ← the DBA would change this
    db.execute("INSERT INTO employees ...");
  }
  generateReport(): string {         // ← Reporting would change this
    return `Report for ${this.name}: $${this.calculatePay()}`;
  }
}

// ───── AFTER: one focused class per actor ─────
class Employee {                     // just the data + identity
  constructor(public name: string, public hours: number, public rate: number) {}
}

class PayCalculator {                // 1 reason to change: Accounting
  calculate(e: Employee): number {
    return e.hours * e.rate;
  }
}

class EmployeeRepository {           // 1 reason to change: the DBA
  save(e: Employee): void {
    db.execute("INSERT INTO employees ...");
  }
}

class ReportFormatter {              // 1 reason to change: Reporting
  format(e: Employee, pay: number): string {
    return `Report for ${e.name}: $${pay}`;
  }
}

References & further reading

5 sources

Knowledge check

Did it land?

Quick questions, answers revealed on submit. Sign in to save your best score.

question 01 / 05

According to SRP, what does "a class should have only one reason to change" actually mean?

question 02 / 05

A class Employee has calculatePay(), saveToDatabase(), and generateReport(). Why is this an SRP violation?

question 03 / 05

Which is the best quick test for spotting an SRP violation?

question 04 / 05

What is the standard refactor for fixing an SRP violation?

question 05 / 05

Which statement about applying SRP is correct?

0/5 answered