<arch.design/>
Principles/Separation of Concerns
{ }CodeArchitecturebeginner1974generaldijkstramodularitycoupling

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/5
{ }
Operates at: Code level

Inside 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

MVC frameworks (Rails, Django, Laravel)

Models handle data, views handle presentation, controllers handle orchestration — three concerns, three directories

CSS / HTML / JS

Structure (HTML), style (CSS), behaviour (JS) are three concerns intentionally separated in web development

AWS IAM

Authentication (who you are) and authorisation (what you can do) are separate concerns with separate APIs and policies

Industry adoption

5/5Ubiquitous — used at virtually every scale-focused company.

Related principles