Coupling Is the Cost: A Working Definition of Modularity
Modularity isn't about file counts or service boundaries — it's about how much you must know, and change, to make a change. A practical model.
Ask ten engineers to define “modular” and you’ll get ten answers about folders, microservices, or line counts. None of those are it. Modularity is a property of change: how much of the system you must understand and touch to safely alter one behavior. Coupling is simply the size of that blast radius.
A blast-radius model
Consider a change to how prices are calculated. In a well-modularized system the change stays inside a boundary. In a coupled one, it ripples outward.
Each dotted edge is a place where one module reached into another’s internals. The goal of modular design is to delete those edges — to make the pricing change stop at the pricing module.
Cohesion is the other half
Coupling asks “how connected are modules?”; cohesion asks “does everything in this module belong together?”. You want low coupling between modules and high cohesion within them. A quick heuristic in TypeScript for spotting a leaky boundary:
// Smell: a module that imports the internals of three others is rarely cohesive.
type ModuleGraph = Map<string, Set<string>>;
function suspiciouslyCoupled(graph: ModuleGraph, threshold = 3): string[] {
return [...graph.entries()]
.filter(([, deps]) => deps.size > threshold)
.map(([name]) => name);
}
The reliability payoff
Modularity isn’t an aesthetic preference — it’s a reliability lever. A bounded blast radius means a bounded failure radius. When the pricing module is truly isolated, a bug in it can’t corrupt reporting, and you can deploy, roll back, and reason about it in isolation. Coupling is the cost you pay on every change and every incident, forever.
Design for the change you’ll make a hundred times, not the diagram you’ll draw once.