Coupling & Cohesion

How tightly pieces of code depend on each other (coupling) versus how well a single piece of code focuses on one clear job (cohesion).

What is it?

Two related but different questions come up constantly when judging whether code is well organized.

The first: if you change this module, how many other modules break or need to change too? If the answer is "a lot," those modules are tightly coupled — they know too much about each other's internals, so a ripple in one becomes a wave everywhere else. If changing one module rarely forces changes elsewhere, they're loosely coupled — which is what you want, because it means you can change one part of the system with confidence about what won't break.

The second, different question: does this one module do a single, clearly-related job, or is it a grab-bag of unrelated logic stuffed together because it was convenient? A module where everything inside it is closely related and works toward one purpose has high cohesion. A module that mixes unrelated responsibilities — say, a "utils.js" that formats dates, sends emails, and calculates taxes — has low cohesion.

The two ideas work together: you want low coupling between modules (so they're independent) and high cohesion within each module (so each one is focused and easy to understand). It's easy to remember which is good: low coupling, high cohesion — loose on the outside, tight on the inside.

Explain like I'm 10

Think of departments in a company. High cohesion means the accounting department only does accounting — not also handling IT support and marketing. Low coupling means accounting can change its internal process without having to call a meeting with every other department first.

Examples

Tight coupling: two modules reaching into each other's internals

// orderService.js
export function createOrder(cart) {
  const order = { items: cart.items, total: cart.items.reduce((s, i) => s + i.price, 0) };
  db.orders.push(order); // reaches directly into a shared global array
  return order;
}

// emailService.js
export function notifyCustomer(customer) {
  const lastOrder = db.orders[db.orders.length - 1]; // assumes it was just created
  send(customer.email, `Your order total is ${lastOrder.total}`);
}

notifyCustomer silently depends on the exact order createOrder pushed to a shared array. Change how orders are stored, or call notifyCustomer before an order exists, and it breaks — the two modules are tightly, invisibly coupled.

Loose coupling: passing what's needed explicitly

// orderService.js
export function createOrder(cart) {
  return { items: cart.items, total: cart.items.reduce((s, i) => s + i.price, 0) };
}

// emailService.js
export function notifyCustomer(customer, order) {
  send(customer.email, `Your order total is ${order.total}`);
}

// calling code
const order = createOrder(cart);
notifyCustomer(customer, order);

notifyCustomer no longer assumes anything about how or where orders are stored — it just needs an order object passed in. Either function can be changed, tested, or reused independently.

How it works

You can spot coupling and cohesion by asking two concrete questions of any module:

- Coupling check: "If I rewrote this module's internals but kept its inputs/outputs the same, would anything else need to change?" If yes, something is leaking through — shared global state, a hidden assumption about call order, or a dependency on another module's private details. - Cohesion check: "Can I describe this module's job in one short sentence, without using the word 'and'?" If you need "and" ("this module validates users and sends emails and logs analytics"), it's doing too many unrelated things.

Why does it exist?

These two properties exist as named concepts because they're the two biggest predictors of how expensive a codebase is to change over time. High coupling means small changes cause wide, unpredictable breakage. Low cohesion means you can never find where a given behavior lives, because related logic is scattered and unrelated logic is jammed together. Naming them gives engineers a shared vocabulary to critique a design before it's built, not just after it's caused pain.

When to use it

Use these as a lens any time you're deciding how to split code into modules, reviewing a pull request, or trying to explain why a piece of code feels hard to work with. They're especially useful when a seemingly small change keeps requiring edits in unrelated files — that is almost always a coupling problem.

When not to use it

Chasing perfectly zero coupling is neither possible nor useful — modules have to call each other to form a working system. The goal isn't zero coupling; it's coupling through clear, stable, minimal interfaces rather than through shared internals and hidden assumptions.

Common mistakes

  • Believing coupling is inherently bad, when the real problem is coupling to internals rather than coupling through a stable, well-defined interface.

  • Creating a 'utils' or 'helpers' file that becomes a low-cohesion dumping ground for anything that didn't obviously belong elsewhere.

  • Reducing coupling by passing around one giant shared object 'just in case' — which just moves the tight coupling into that object's shape.

Practice exercises

  1. Easy:

    Look at a 'utils.js' or 'helpers.js' file in a project you've worked on. List its functions and note which ones are unrelated to each other — that's a cohesion problem.

  2. Medium:

    Rewrite the tightly-coupled order/email example above so that notifyCustomer no longer needs to know anything about how orders are represented internally, beyond the fields it actually uses.

  3. Hard:

    Describe a module you've encountered with low cohesion (many unrelated responsibilities). Propose how you'd split it into two or more high-cohesion modules, and explain what interface each would expose.

Interview questions

What's the difference between coupling and cohesion?

Coupling measures how dependent modules are on each other's internals — you want this low. Cohesion measures how focused a single module's responsibilities are — you want this high.

Why is 'low coupling, high cohesion' considered good design?

Low coupling means you can change one module with confidence that unrelated modules won't break. High cohesion means each module is easy to understand, test, and locate, because it does one clearly-related job.

Give an example of tight coupling that isn't obvious from reading a single function.

Two modules that communicate through a shared global variable or shared mutable state, where one module assumes the other has already run and set that state up — the dependency is invisible until something breaks.