The idea
What it is
Almost every time a senior engineer calls a design "clean" or "messy," they're really reading two dials. Cohesion asks: does this module do one focused thing, or a grab-bag of unrelated things? Coupling asks: how much does this module need to know about other modules to do its job? The goal is simple to state — high cohesion, low coupling — and most refactoring is just turning those two dials in the right direction.
Think of a well-run kitchen. The pastry station does pastry, the grill does grilled food, the dishwasher washes — each station is cohesive, focused on one kind of work. And they hand off through a simple counter, not by reaching into each other's drawers — that's low coupling. Now imagine one chaotic station that fries, bakes, plates, and takes payment, constantly grabbing tools from everyone else. That's low cohesion and high coupling: slow, fragile, and impossible to change without chaos.
The one sentence to remember
High cohesion = each module does one thing well. Low coupling = modules barely depend on each other. Push both directions at once and the design almost always gets better.
Mechanics
How it works
Cohesion — how focused a module is
Cohesion measures how related the things inside a single module are. A highly cohesive InvoiceFormatter does invoice formatting and nothing else — every method pulls in the same direction. A low-cohesion module is a junk drawer: a Utils or OrderManager class that validates orders, charges cards, sends emails, and writes to the database. The classic warning sign is a name with Manager, Helper, Util, or Processor in it, plus a method list that reads like four unrelated jobs stapled together — a God class.
There's a well-known spectrum here, from worst to best: coincidental (random things thrown together) → logical → temporal → procedural → communicational → sequential → functional (everything contributes to one single, well-defined task). You don't need to memorize the ladder — just aim for the top: one module, one job.
Coupling — how much modules depend on each other
Coupling measures how much one module has to know about another. Highly coupled code is the kind where you change one class and five others break, because they all reached into each other's internals or hard-wired each other's concrete types. The tell-tale symptom: a small feature request touches files all over the codebase, and a one-line fix triggers a cascade of "oh, I also need to update…".
Coupling also has a spectrum, from worst to best: content (one module pokes at another's private internals) → common (shared global/mutable state) → control → stamp → data (modules talk only through small, explicit parameters). The further toward data coupling you sit, the safer change becomes.
Why they trade off with naive splitting
Here's the subtle part: cohesion and coupling pull against each other if you split carelessly. Chop a focused class into ten tiny pieces and each piece looks "cohesive" — but now they all have to call each other constantly, and coupling skyrockets. The art isn't more modules or fewer modules; it's drawing the boundaries along the seams of responsibility so that the things that change together live together, and the lines crossing between modules are few and thin.
Reducing coupling with interfaces (DIP)
The strongest lever for cutting coupling is depending on an interface instead of a concrete class. If OrderService news up a StripeGateway directly, it's welded to Stripe. If instead it accepts a PaymentGateway interface and is handed an implementation, it no longer knows or cares which gateway it talks to:
// ❌ tightly coupled — OrderService is welded to a concrete class
class OrderService {
private gateway = new StripeGateway(); // hard dependency
}
// ✅ loosely coupled — depends on an interface, gateway is injected
interface PaymentGateway { charge(cents: number): void; }
class OrderService {
constructor(private gateway: PaymentGateway) {} // dependency injected
}
// now swapping Stripe → PayPal touches ONE wiring line, not OrderServiceThis is the Dependency Inversion Principle: high-level modules and low-level modules both depend on an abstraction, not on each other. The interface becomes a thin contract — a small, stable line crossing the module boundary — instead of a thick web of concrete dependencies. Swapping implementations, mocking in tests, and evolving one side without breaking the other all become easy.
Cohesion and the Single Responsibility Principle
High cohesion is essentially the Single Responsibility Principle seen from the inside: "a module should have one reason to change." If two unrelated forces (say, how we tax orders and how we format emails) can each force the same class to change, that class is doing two jobs — split it.
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
Eight responsibilities start dumped into one tangled module — low cohesion, lots of cross-cutting dependency lines. Click a function to move it into the module it belongs to (Orders, Billing, or Notifications) and watch the coupling score fall and cohesion rise as related work gets grouped. Hit Auto-refactor to snap to the ideal, or Tangle it to make a mess again.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Move a function to its home module
The board loads tangled: all eight functions sit in one Unsorted pile with dependency lines crossing everywhere. Click chargeCard, then pick Billing as its destination. Watch the log narrate moved chargeCard → Billing: coupling 9→7 and see the coupling bar drop as that edge stops crossing a boundary.
Watch cohesion rise as you group
Keep moving related functions together — calcTax and formatInvoice into Billing, sendEmail and logEvent into Notifications, the order functions into Orders. The cohesion indicator climbs as each module fills up with related members, while the coupling score keeps falling. Improving both at once is the whole game.
Auto-refactor vs. Tangle it
Press Auto-refactor to snap every function into its ideal module — coupling bottoms out and cohesion maxes, showing you the target state. Then hit Tangle it to dump everything back into one module and watch both metrics collapse. The contrast makes "high cohesion, low coupling" visible at a glance.
In practice
When to use it — and what trips people up
When to reach for these dials
Use cohesion and coupling as your everyday review lens, not just for big redesigns. Whenever a class name turns vague (Manager, Util, Helper), whenever one method does several unrelated jobs, or whenever a tiny change ripples across many files, the dials are telling you to split for cohesion and introduce an interface for coupling. The payoff is biggest exactly where code changes most — at the seams between subsystems and around third-party integrations.
Don't chase the metrics off a cliff
"More modules" is not the goal — better boundaries is. Splitting a focused class into a dozen anemic fragments that constantly call each other trades good cohesion for awful coupling. Group what changes together; only draw a boundary where there's a real seam.
What it gives you
- Changes stay local — a focused, loosely coupled module can be modified without rippling across the codebase.
- Easier to test in isolation — depend on interfaces and you can mock collaborators trivially.
- Independent evolution and reuse — swap implementations (Stripe → PayPal, MySQL → Postgres) by changing one wiring line.
- Easier to read and reason about — one module, one job, with thin, explicit lines crossing its boundary.
Common mistakes
- Over-splitting into anemic fragments — too many tiny classes that do almost nothing but call each other adds coupling and noise.
- Hidden coupling sneaks back through shared globals, singletons, or event buses that don't show up as obvious dependencies.
- Premature abstraction — adding interfaces and indirection before you actually need to swap anything just slows you down.
- Metrics are a guide, not a target — chasing a cohesion/coupling number blindly can produce technically 'clean' but harder-to-follow code.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// ❌ BEFORE: one God class — low cohesion, tightly coupled to concretes
class OrderManager {
place(order: Order) {
if (!order.items.length) throw new Error("empty order"); // validation
const tax = order.subtotal * 0.2; // tax
new StripeGateway().charge(order.subtotal + tax); // payment (concrete!)
new MySqlDb().save(order); // persistence (concrete!)
new SmtpMailer().send(order.email, "Thanks!"); // notification (concrete!)
}
}
// ✅ AFTER: focused classes, each depending on small interfaces
interface PaymentGateway { charge(cents: number): void; }
interface OrderRepo { save(order: Order): void; }
interface Mailer { send(to: string, body: string): void; }
class TaxCalculator { taxFor(o: Order) { return o.subtotal * 0.2; } }
class OrderValidator { validate(o: Order) { if (!o.items.length) throw new Error("empty order"); } }
class OrderService {
constructor(
private validator: OrderValidator,
private tax: TaxCalculator,
private pay: PaymentGateway, // interface, injected
private repo: OrderRepo, // interface, injected
private mailer: Mailer, // interface, injected
) {}
place(order: Order) {
this.validator.validate(order);
const total = order.subtotal + this.tax.taxFor(order);
this.pay.charge(total);
this.repo.save(order);
this.mailer.send(order.email, "Thanks!");
}
}
// swapping Stripe → PayPal, or MySQL → Postgres, never touches OrderService.References & further reading
5 sources- Articleen.wikipedia.org
Wikipedia — Coupling (computer programming)
The full coupling spectrum from content coupling down to data coupling, with clear definitions of each level.
- Articleen.wikipedia.org
Wikipedia — Cohesion (computer science)
The cohesion ladder from coincidental up to functional, the gold standard you should aim for.
- Articleen.wikipedia.org
Wikipedia — Single responsibility principle
High cohesion stated as a design rule: a module should have one, and only one, reason to change.
- Book
Structured Design — Larry Constantine & Ed Yourdon
The classic that introduced coupling and cohesion as measurable design qualities; the origin of the whole vocabulary.
- Book
Clean Architecture — Robert C. Martin
Modern treatment connecting cohesion, coupling, the SOLID principles, and dependency inversion across module boundaries.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Nothing is scored or saved.
question 01 / 05
What do high cohesion and low coupling each describe?
question 02 / 05
A single OrderManager class validates orders, charges cards, saves to the DB, and sends emails. Which problem is this?
question 03 / 05
You change one class and five others break. Which design quality is poor?
question 04 / 05
How does depending on an interface instead of a concrete class (DIP) reduce coupling?
question 05 / 05
Why can splitting a class too aggressively actually hurt the design?
0/5 answered