Skip to main content

Conceptbeginner

Separation of Concerns

Overview

Separation of concerns is the principle that each part of the system should handle one subject, and that distinct subjects should live in distinct parts.

The entire difficulty lies in "subject". The principle is trivially accepted and rarely applied well, because defining what counts as a separate subject is where the judgement happens.

Problem

Code that mixes responsibilities is hard to change for a precise reason: to alter one subject, you have to understand and avoid breaking the others living in the same place.

A function that validates input, applies a business rule, formats output and writes to the database has four reasons to change. Any one of them forces you to reread the other three and risk breaking them.

The compound effect is what matters: each additional subject in the same place does not add difficulty, it multiplies it. Four mixed subjects are not four times harder to change than one — they are far more, because the interactions between them also have to be held in your head.

Core Concepts

The axis is the reason for change

The operational criterion, and the same one that guides modularity: two things are distinct subjects if they change for different reasons.

Format validation changes when the input contract changes. A business rule changes when the company decides differently. Persistence changes when the schema or the technology changes. Three independent reasons, three subjects.

That is the practical formulation of the Single Responsibility Principle, which speaks of "one reason to change" and is frequently read as "do only one thing" — a reading that leads to fragmenting code without criteria.

Cross-cutting concerns

Some subjects run through the whole system: logging, authentication, transactions, telemetry, error handling.

They cannot be isolated in a module because they need to be everywhere. Separation here is achieved by another mechanism — middleware, decorators, interceptors, aspects — that keeps the subject in one place and applies it in many.

Mixing cross-cutting concerns into business code is the most common violation of the principle, and the most tolerated.

Separating is not fragmenting

The principle says distinct subjects stay separate. It does not say everything must be small.

Applying it mechanically produces the opposite problem: dozens of tiny units that always change together, between which the reader has to jump to understand a flow. The separation raised the cost of reading without lowering the cost of change — because there were no independent reasons for change to begin with.

If two things always change together, they are one subject, and separating them is a mistake.

Mental Model

Count the reasons this code would change.

If the list has more than one item and the items are independent, there are mixed responsibilities. If it has more than one item but they always occur together, it is a single subject — and separating would be damage.

When to Use

  • When a piece of code has genuinely independent reasons for change.
  • When one subject needs to be tested without the others.
  • When different people need to change different subjects in the same flow.
  • When a cross-cutting concern is scattered and duplicated.
  • When a subject needs to be replaced — swapping the payment provider without touching the billing rule.

When Not to Use

When the subjects always change together. This is the most important case and the most ignored. Separating here produces pure indirection: the reader jumps between files and gains nothing, because they will never change one without the other.

When the separation requires an abstraction that does not hold. Separating a business rule from persistence is easy to state and sometimes expensive to do — if the rule depends on an aggregation only the database performs efficiently, forcing the separation produces either a leaking abstraction or unacceptable performance.

In disposable code. A prototype, a migration script, a one-off analysis. Separating responsibilities is an investment in future change; where there will be no future change, the investment does not pay.

When the cost of navigation exceeds the cost of change. A flow that spans eight files to do what would fit readably in one is over-separated. The symptom is needing a diagram to understand a simple operation.

Alternatives

  • Cohesion by proximity — keeping together what changes together, without imposing formal separation. It is the correct default until independent reasons appear.
  • Separation by convention — cheaper and less reliable than enforced separation.
  • Aspects and middleware — for cross-cutting concerns, the correct alternative to separation by module.

Trade-offs

The axis is cost of changing one subject versus cost of understanding the whole flow.

More separationLess separation
Change isolated to one subjectChange requires understanding neighbours
Each part testable aloneTesting carries everything
Replacing one subject is viableReplacement touches everything
The flow requires jumping between filesThe flow reads linearly
Abstractions to maintain and justifyNo intermediate abstraction

It is worth noting that both sides fail the same way at the extreme: code that is impossible to understand. The causes are opposite.

Failure Modes

A leaking abstraction. The separation exists formally, but the consumer needs to know how the other side works. A repository that returns ORM structures did not separate persistence from domain.

Separation on the wrong axis. Subjects that change together were separated; those that change independently stayed together. Worse than not separating, because it has the cost without the benefit.

Scattered cross-cutting concern. Error handling replicated in every function, with subtle variations. When the policy changes, it changes in thirty places — and in two of them somebody forgets.

Anaemic layers. Layers that exist for symmetry and merely forward calls, handling no subject at all. Navigation cost with no real separation.

Common Mistakes

Reading the Single Responsibility Principle as "do only one thing". It leads to fragmenting without criteria. The correct formulation is about reasons for change.

Separating by technical type rather than by subject. All validators in one place, all mappers in another. It groups what changes for different reasons.

Accepting a mixed cross-cutting concern out of convenience. Logging and transactions inside the business rule look harmless and are the most common violation.

Confusing this with layers. Layers are one way to separate responsibilities, not the definition of it. A layered system can have tremendously mixed responsibilities inside each layer.

Real-World Example

An order-processing function, 180 lines long, did the following: validate the payload, check stock, calculate taxes, apply a discount, persist, emit an event and send an email.

The question applied was "how many reasons for change?". The answer revealed something the line count did not.

Five distinct reasons: the input contract, the tax rule, the commercial discount policy, the persistence schema, and the notification integration.

But two things that looked separable — checking stock and persisting the order — always changed together, because the stock reservation was part of the same transaction. Separating them would have created an abstraction that would need to be punctured on the first change.

The result: five units, not seven. And the most valuable one was the tax rule, which became changeable and testable without touching anything else — which mattered because tax rules change by external decision, on a deadline, several times a year.

  • Modularity — the structure that materializes the separation.
  • Cohesion — the measure of whether what stayed together belongs together.
  • Abstraction — the mechanism that makes separation possible.

Practical Exercise

Pick the longest function in your system and list the reasons it would change. Be specific: not "a rule change", but "when the tax team alters the rate".

For each pair of reasons, ask: has one ever changed without the other?

Only the pairs that have already diverged justify separation. The rest are one subject.

Interview Questions

  • How do you identify mixed responsibilities?
  • When does separating responsibilities make the code worse?
  • What is the difference between separation of concerns and layers?

Further Exploration

  • Dijkstra, Edsger. On the role of scientific thought, 1974 — the origin of the term.
  • Martin, Robert C. Clean Architecture. Prentice Hall, 2017 — the Single Responsibility Principle in terms of reasons for change.
Finished reading this document?Your progress is saved in this browser only.