Separation of Concerns
Divide a program into distinct sections where each section addresses one concern — so changes to one area don't ripple unexpectedly into unrelated areas.
★★★★★5/5Inside a codebase — classes, modules, files
How it works
Edsger Dijkstra coined 'Separation of Concerns' in his 1974 paper 'On the role of scientific thought'. He observed that human intellectual ability is limited — the only way to tackle complex problems is to focus on one aspect at a time, keeping other aspects 'from interfering with full attention'.
In software, a 'concern' is any dimension of a system that can be independently reasoned about: data access, business logic, presentation, authentication, logging, error handling. When multiple concerns are mixed in one module, changes to one concern disturb the others.
SoC is the motivation behind nearly every major architectural pattern. Layered Architecture separates presentation from logic from persistence. MVC separates models, views, and controllers. Clean Architecture and Hexagonal Architecture separate domain from infrastructure. CSS separates style from HTML structure.
At the code level, SoC manifests in small decisions: don't embed SQL in your business logic; don't put formatting code inside your calculation function; don't handle authentication inside your order processing handler.
Implementation
TypeScript · Go · Rust// ❌ Data fetching, business logic, and presentation all tangled together
async function renderUserCard(userId: string): Promise<string> {
const res = await fetch(`/api/users/${userId}`); // data fetching in presentation
const raw = await res.json();
const name = `${raw.firstName} ${raw.lastName}`; // business logic in presentation
const init = `${raw.firstName[0]}${raw.lastName[0]}`.toUpperCase();
return `<div class="card"><span class="avatar">${init}</span>${name}</div>`;
}
// ✓ Each layer does one job; each can change without touching the others
// ── Data layer ──────────────────────────────────────────────────────────
async function fetchUser(id: string): Promise<RawUser> {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// ── Domain / transformation layer ───────────────────────────────────────
interface User { name: string; initials: string; }
function toUser(raw: RawUser): User {
return {
name: `${raw.firstName} ${raw.lastName}`,
initials: `${raw.firstName[0]}${raw.lastName[0]}`.toUpperCase(),
};
}
// ── Presentation layer ──────────────────────────────────────────────────
function renderUserCard(user: User): string {
return `<div class="card"><span class="avatar">${user.initials}</span>${user.name}</div>`;
}
// Composed at the boundary — each concern independently testable
async function showUserCard(userId: string): Promise<string> {
return renderUserCard(toUser(await fetchUser(userId)));
}Why it matters
Mixed concerns create tight coupling. A change to database schema propagates into business logic. A UI redesign requires touching data access code. SoC creates boundaries that let each concern change independently — and makes it possible to understand, test, and replace any concern in isolation.
✓ When to use
- →Always — SoC is a foundational principle, not an advanced technique
- →When you find yourself putting 'just a little bit' of SQL in your service layer
- →When UI code starts to contain business rules
- →When authentication, logging, or caching logic is repeated throughout business code
✗ When NOT to use
- →Don't create so many layers that simple operations require touching dozens of files
- →In tiny scripts, strict separation adds overhead with no benefit
Trade-offs
Each concern can be changed, tested, and understood independently
Too many layers add indirection — simple operations span many files
Teams can own specific concerns without stepping on each other
Identifying the right concerns requires domain understanding — wrong cuts create leaky boundaries
Enables specialisation — frontend, backend, data engineers each own their concern
Cross-cutting concerns (logging, auth, transactions) resist clean separation
In production
Models handle data, views handle presentation, controllers handle orchestration — three concerns, three directories
Structure (HTML), style (CSS), behaviour (JS) are three concerns intentionally separated in web development
Authentication (who you are) and authorisation (what you can do) are separate concerns with separate APIs and policies
Industry adoption
Related principles
Single Responsibility Principle
A class should have only one reason to change — keep each unit focused on a single job so unrelated concerns don't accidentally break each other.
DRY — Don't Repeat Yourself
Every piece of knowledge should have a single, authoritative representation — duplication forces you to keep multiple copies in sync, and they inevitably drift.
Layered (N-Tier) Architecture
LiveOrganise code into horizontal layers (Presentation, Business Logic, Data Access) where each layer only calls the one below it.
Hexagonal Architecture
Place the domain at the centre and connect all I/O (HTTP, database, messaging) through explicit ports and adapters — so the core is framework-free and infinitely testable.