SOLID Principles · The Five Principles

Single Responsibility Principle

Give each class one reason to change by separating responsibilities that change for different people and different reasons.

  • 4 min read
  • 11 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The principle
  3. How to apply it, step by step
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

The Single Responsibility Principle says a class should have one, and only one, reason to change. It's the most quoted SOLID principle and the most misread: it isn't about doing "one thing" or having one method, but about not letting unrelated kinds of change collide in the same code. This skill covers what a "reason to change" is, how to find the seams in a class, and how to avoid splitting too far.

Here's the kind of class it's about:

Java

class Invoice {
    double calculateTotal() { /* tax and discount rules */ }
    String toPdf()          { /* fonts, layout, margins */ }
    void saveToDatabase()   { /* SQL, connection handling */ }
}

Python

class Invoice:
    def calculate_total(self) -> Money: ...     # tax and discount rules
    def to_pdf(self) -> bytes: ...              # fonts, layout, margins
    def save_to_database(self) -> None: ...     # SQL, connection handling

Three different groups care about this class: the finance team (tax rules), the design team (the PDF layout) and whoever owns the database schema. A change requested by any of them edits the same file - and a margin tweak can accidentally break a tax calculation that shares a helper method.

We'll go from simple to more technical:

  1. The principle, and what "a reason to change" means.
  2. Splitting a class along its reasons to change, step by step.
  3. Where you'll see it in real architectures.
  4. How far to split - and when splitting makes things worse.

The principle

A class should have one, and only one, reason to change.

"Reason to change" means a source of change - usually a person or team with a distinct concern. Split the class so each part changes for one reason:

Java

class InvoiceCalculator { Money total(Invoice inv) { ... } }         // finance rules
class InvoicePdfRenderer { byte[] render(Invoice inv) { ... } }      // presentation
class InvoiceRepository { void save(Invoice inv) { ... } }           // persistence

Python

class InvoiceCalculator:
    def total(self, inv: Invoice) -> Money: ...         # finance rules


class InvoicePdfRenderer:
    def render(self, inv: Invoice) -> bytes: ...        # presentation


class InvoiceRepository:
    def save(self, inv: Invoice) -> None: ...           # persistence

Now a new tax rule touches only InvoiceCalculator, and a layout change can't break it. In Python the unit is often a module rather than a class - pricing.py, pdf.py and storage.py, with plain functions where no state is needed; the principle is the same.

How to apply it, step by step

  1. List who would ask for changes to this class, and why.
  2. Group the methods by those reasons - that's where the seams are.
  3. Move each group into its own class with a name that states its one job.
  4. Let a small coordinating class (or the caller) wire them together.
  5. Stop when each class has one clear reason to change - not when every class has one method.

Where you'll see it

  • Layered architectures: controllers (HTTP), services (business rules) and repositories (storage) change for different reasons.
  • Separating what to compute from how to present or store it - calculators vs. formatters vs. repositories.
  • Logging, metrics and security pulled out of business classes into filters, decorators or aspects.

Which tool for which job?

You noticeDo this
Different teams or stakeholders ask for changes to one classsplit it by who asks: one class per actor
Calculation, presentation and storage in one classcalculator, renderer/formatter, repository
A shared private helper used by two different concernsgive each concern its own copy, or an explicit shared rule both own
Logging, metrics or security mixed into business methodsdecorators, middleware or filters around the business class
Several small classes that always change togethermerge them back - they're one responsibility

Common mistakes

MistakeWhat happensDo this instead
One method per classten files to follow one featuresplit by reasons to change, not method count
Splitting by technical layer onlyclasses that always change together, spread thinkeep what changes together, together
Names with "And", Manager, Helper, Utilseveral jobs hidden behind a vague namename each class after its one job
Sharing a helper between two actors to avoid duplicationone team's change silently breaks the other's resultsduplicate it - it only looked the same

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. UserService validates input, hashes passwords, stores users and sends welcome emails. How many reasons to change?

Show answer

At least four: validation rules, security/hashing policy, storage, and email content/delivery. Each is a candidate for its own class, with UserService coordinating them.

2. Is "one method per class" what SRP asks for?

Show answer

No - SRP is about one reason to change. A class can have many methods that all serve the same concern.

3. What's a quick red flag for an SRP violation in a class name?

Show answer

The word "And" (or vague names like Manager, Helper, Util) that hide several jobs.

Key takeaways

  • One class, one reason to change - one actor it answers to.
  • Find seams by asking who requests changes and why.
  • Split calculation, presentation and persistence; coordinate them from outside.
  • Don't over-split: things that always change together belong together.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You noticeDo this
Several teams change one classsplit it by actor
Calculation + presentation + storage togethercalculator, renderer, repository
A helper shared by two concernsseparate copies, each owned by one concern
Logging/metrics/auth inlinedecorators, middleware, aspects
Tiny classes that always change togethermerge them

Snippets worth knowing by heart

Java

class InvoiceCalculator { Money total(Invoice inv) { /* finance rules */ return null; } }
class InvoicePdfRenderer { byte[] render(Invoice inv) { /* layout */ return null; } }
class InvoiceRepository { void save(Invoice inv) { /* storage */ } }

Python

class InvoiceCalculator:
    def total(self, inv: Invoice) -> Money: ...        # finance rules

class InvoicePdfRenderer:
    def render(self, inv: Invoice) -> bytes: ...       # layout

class InvoiceRepository:
    def save(self, inv: Invoice) -> None: ...          # storage

The traps

  • One method per class - SRP is about reasons to change, not size.
  • Splitting by layer only - classes that always change together.
  • "And", Manager, Util names - several jobs behind one name.
  • Shared helpers across actors - one team's change breaks another's.

Staff-level points, one line each

  • Martin's restatement: responsible to one actor - whose requests land here?
  • Accidental duplication looks alike but serves different actors - keep it; "duplication is far cheaper than the wrong abstraction".
  • A shared function growing per-caller flags is the wrong abstraction - re-inline it.
  • Divergent change = too many reasons in one class; shotgun surgery = one change across many classes.
  • LCOM4 counts connected method groups: 3 for a mixed UserService, but 1 for an Invoice whose methods all read self.lines - a hint, not a verdict.
  • Change history (who changes which methods together) is better evidence than any metric.
  • Cross-cutting concerns live in decorators, middleware and aspects.
  • At scale: Common Closure Principle, bounded contexts, Conway's law.

Say it in an interview

  • "Finance and operations both change this class, so it answers to two actors - I'd split it."
  • "These two functions look the same but belong to different teams, so I'd keep them separate."
  • "Adding a field touches six classes - we've over-split; I'd merge them."
  • "Timing goes in a decorator, not in every repository method."

Practice

This skill comes with 11 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Single Responsibility Principle free

Payslips: Calculate, Format, Archive

EasyFreeCoding problemJava · Python

A payroll system used to have one Payslip class that computed tax, formatted the text and stored the result. Three different teams kept editing it - finance (tax rules), design (the layout) and records (storage) - and their changes kept colliding.

The Overtime Rule That Broke the Report

MediumProCoding problemJava · Python

Two teams use the same employee data for different purposes:

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Single Responsibility Principle free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime