Coupling and Cohesion
Date: 2026-08-17
How much things depend on each other, and how much a thing’s contents belong together. Nearly every architecture argument — microservices, modules, component boundaries — is one of these two in disguise, which makes them the most useful pair of words in the vocabulary.
Coupling measures how much one component depends on another. Cohesion measures how strongly a component’s own parts belong together.
The goal is low coupling and high cohesion. They pull in opposite directions, which is why it’s a judgement rather than a rule.
HIGH COHESION LOW COHESION
everything in this a "utils" file
module is about containing date
ORDERS formatting, an API
wrapper and a colour
converter
LOW COUPLING HIGH COUPLING
change orders without changing orders
touching shipping breaks shipping,
email and reporting
Kinds of coupling, worst to best
CONTENT A reaches into B's internals
reading private state, monkey-
patching
← worst. B cannot change anything
COMMON both share mutable global state
← a change anywhere affects
everywhere
CONTROL A passes a flag telling B WHICH
branch to take
render(data, isAdmin, isCompact)
← A knows B's internal structure
STAMP A passes a whole object when B
needs two fields
← B now depends on that shape
DATA A passes exactly what B needs
← the goal for most code
MESSAGE A emits an event; B may or may
not listen; neither knows the
other
← loosest. Also the hardest to
trace
Control coupling is the one to spot in review. A boolean parameter that switches behaviour usually means two functions wearing one name:
BEFORE AFTER
render(data, isCompact) renderFull(data)
renderCompact(data)
The signals
HIGH COUPLING SMELLS
one change requires edits in five files
you can't test A without instantiating
B, C and D
a shared "constants" or "types" file
everything imports
changing a database column breaks the
front end
LOW COHESION SMELLS
a file named utils, helpers, common,
or misc
a module you cannot describe in one
sentence
a class where half the methods use one
field and half use another
The utils file is the canonical low-cohesion artefact, and it grows because nobody has to decide where anything goes.
The tension
You cannot minimise both. Splitting a module to raise cohesion creates a dependency between the halves, which raises coupling. Merging to reduce coupling lowers cohesion.
one giant module coupling 0 internally
cohesion terrible
fifty tiny modules cohesion excellent
coupling everywhere
The useful question is not “how do I minimise both” but “where do I want the seam?” Put boundaries where change is unlikely to cross them — group by what changes together, not by what looks similar.
BY TYPE (common, often wrong)
/components /hooks /services
→ one feature change touches all three
BY FEATURE
/checkout /catalogue /account
→ a feature change stays in one folder
Where it shows up in the vault
- Microservices versus monolith is entirely this argument. Services lower coupling at the cost of network calls, distributed transactions and operational overhead — a real trade, not a free win
- Component API design — every prop is a coupling point, which is why the advice is to expose less — Component API Design
- Design tokens exist to decouple appearance from components — Design Tokens
- Immutability removes a coupling nobody declared: two modules sharing a mutable object are coupled through it — Immutability
- Database constraints versus application checks — where the rule lives determines who is coupled to it — The Relational Model
The honest caveat
Decoupling has a cost, and it’s frequently over-applied. An abstraction layer added for a second implementation that never arrives is pure overhead — indirection to read through, tests to maintain, and a boundary that constrains the one implementation you have.
Couple things that genuinely change together. Premature decoupling is the same mistake as premature optimisation, and it’s harder to undo — Abstraction and Leaky Abstractions.