InterviewKata
Software architecture · Level 2: Structure · Module 07 of 46

Modularity, cohesion and coupling

Every architecture is a way of splitting a system into parts. Whether the split helps or hurts depends on two measurable things: how well each part holds together (cohesion) and how much the parts depend on each other (coupling). Get them right and a change stays inside one part; get them wrong and every change ripples through half the system, whatever the architecture diagram says.

1. The idea

A kitchen. A good kitchen has stations: everything for baking in one place, everything for the grill in another. That's cohesion: things used together are kept together. The stations depend on each other as little as possible: the baker never has to walk to the grill to get flour. That's low coupling. A kitchen where the flour is by the grill, the oven gloves in the pantry and every station shares one knife is technically complete, and impossible to work in.

Modularity means grouping related code into parts with clear boundaries: packages, namespaces, components, services. Two properties tell you whether the grouping is good:

Cohesion: how well a part holds together

High cohesion means everything inside serves one purpose and changes for the same reasons. A "Pricing" component that calculates prices, discounts and tax is cohesive; a "Utils" package with date formatting, email sending and PDF generation is not: it has no reason to exist except convenience.

Coupling: how much parts depend on each other

Afferent coupling (Ca) counts incoming dependencies: how many parts use this one. Efferent coupling (Ce) counts outgoing ones: how many parts this one uses. A part with high afferent coupling is risky to change, because every change reaches all its users.

Three numbers that describe a component

Kinds of cohesion, best to worst

KindParts are together because...
Functionalthey all contribute to one well-defined task (best)
Sequentialthe output of one is the input of the next
Communicationalthey work on the same data
Proceduralthey must run in a certain order
Temporalthey run at the same time (start-up, nightly jobs)
Logicalthey're the same kind of thing but unrelated (all "string helpers")
Coincidentalnothing: they just ended up in the same file (worst)

2. How it works

Afferent coupling is blast radius

Six components of an online shop. Box text shows each one's incoming and outgoing dependencies. Watch two changes of the same size have very different reach.

The Law of Demeter: talk only to your neighbours

A component should call only the things it directly knows, not reach through them into their internals. Code like order.getCustomer().getAddress().getPostcode() couples the caller to three classes' structure. Watch what happens when one of them changes.

Stamp coupling: passing too muchPassing a whole object when the receiver needs one field couples it to the object's entire shape, and costs bandwidth across a network. A 50 KB customer profile sent 2,000 times a second is 100 MB/s; the 200-byte name and email it actually needs is 0.4 MB/s. Send only what's needed, or let the receiver ask for it.

3. Try it yourself

Coupling metrics calculator

The matrix says which component depends on which (row uses column). Toggle dependencies and set how abstract each component is. The chart plots each one's instability against its abstractness: aim for the dashed main sequence and stay out of the coloured corners.

Tools such as JDepend (Java), NDepend (.NET) and dependency-cruiser (JavaScript) compute these numbers from real code. In an architecture review, run them and look first at the components in the zone of pain.

4. Trade-offs

Reuse vs coupling

A shared component avoids duplicated code, and makes everything that uses it change when it changes. Reuse stable, abstract things freely; think twice before sharing fast-changing business logic (Module 06's lab has this decision).

Abstraction vs simplicity

Interfaces move a stable component out of the zone of pain, but every interface is indirection. Add them where many depend on a component, not everywhere.

Fine vs coarse parts

Many small parts are each cohesive but add coupling between them; a few big parts have little coupling but mix concerns. The right size groups code that changes together.

Demeter vs convenience

Following the Law of Demeter adds small delegating methods (order.shippingPostcode()). They're boilerplate, and they're what lets the internals change freely.

5. In real projects

Modularity is the architecture's foundation. Every style in Level 3 is a choice of where to put module boundaries and how strongly to enforce them: inside one codebase (layered, modular monolith), or as separate deployments (service-based, microservices). A codebase with poor modularity doesn't get better by being split into services; it becomes a distributed mess.

Watch the "common" and "shared" packages. They're where the zone of pain usually lives: concrete code with dozens of dependents. Split them by purpose, make the stable parts abstract, and move the business logic back to the components that own it.

Check it automatically. Coupling drifts quietly. A test that fails when a forbidden dependency appears ("nothing in Orders may use Reports") keeps the structure honest (Module 26).

Say it in the interview

Two-sentence answers
  • "Cohesion vs coupling?" Cohesion is how well the code inside a part belongs together; coupling is how much parts depend on each other. We want high cohesion and low coupling, so changes stay inside one part.
  • "What's afferent coupling?" The number of parts that depend on this one: its blast radius. High afferent coupling means changes are risky, so such a component should be stable and preferably abstract.
  • "What's the Law of Demeter?" Only talk to your immediate collaborators, never reach through them into their internals. It keeps callers from depending on the structure of classes they don't own.
Red flags interviewers notice
  • A "common" module that everything depends on, full of business logic.
  • Proposing microservices to fix tangled code without fixing the modularity first.
  • Passing whole entities between services "in case they're needed".

6. Remember it

Memory hook: "Things that change together, live together"That's cohesion. And the more things lean on a part, the more stable and abstract it must be.
How this hook works: what "things that change together, live together" means

What the slogan means, phrase by phrase

"Things that change together"
Code that changes for the same business reason, at the same time.
"live together"
Belongs in the same component, so one change touches one part.

Why this picture fits: it defines cohesion by its purpose (local change) and implies low coupling: if things that change together are together, few changes cross a boundary.

The story → the concept

The kitchenThe code
A baking station with everything for bakingA functionally cohesive component
The baker never walks to the grillLow coupling
The one knife every station sharesA component with high afferent coupling
A drawer of random gadgetsCoincidental cohesion ("Utils")

The trap it saves you from

Grouping code by what it looks like ("all the helpers") instead of what it's for.

7. Common mistakes

The shared kernel of everythingA "common" module with business logic that every service imports. Every change to it is a coordinated release across the system.
Train-wreck callsChains like a.getB().getC().doSomething() spread knowledge of internal structure to every caller.
Splitting by technical typeAll controllers in one package, all repositories in another: logical cohesion. A business change touches every package.
Abstracting everythingInterfaces on components nobody depends on add indirection and land in the zone of uselessness.

8. Design exercise (30 minutes)

  1. Draw the dependencies (10 min). For a codebase you know, list its top-level packages or modules and draw who depends on whom. Count Ca and Ce for each.
  2. Find the pain (10 min). Which component has the highest afferent coupling? Is it abstract? What changed in it last month, and how many other parts had to change?
  3. Fix one (10 min). Pick one train-wreck call or one over-shared object in your code and rewrite it so the caller asks for exactly what it needs.

9. Self-check quiz

A component has high afferent coupling. What does that mean?

Afferent = incoming. Its changes reach every dependent.

Ca = 6, Ce = 0. What is its instability?

I = Ce / (Ca + Ce) = 0 / 6 = 0: maximally stable, hard to change.

What puts a component in the zone of pain?

Low I and low A: D is near 1. Make it abstract or reduce its dependents.

How do you fix order.getCustomer().getAddress().getPostcode()?

The caller talks only to its neighbour; the internals can change behind it.

Which kind of cohesion is the strongest?

Everything in the part contributes to one well-defined task.

10. Go deeper

ResourceTimeWhy
Fundamentals of Software Architecture, 2nd edition (Richards and Ford)bookChapter 3 covers modularity, cohesion, coupling and the main sequence.
Software package metrics (Robert C. Martin's metrics, summarised)10 minThe original definitions of Ca, Ce, I, A and D.
Law of Demeter10 minThe principle and its origin, with examples.