Chapter 3: Modularity
Introduction
As frontend applications scale, structural decay often appears long before visible bugs. Components become harder to change, teams slow down, and small changes ripple unexpectedly across the codebase. This chapter frames modularity as the primary structural defense against that decay—less as an abstract ideal, more as an ongoing architectural effort that requires attention, energy, and trade-off awareness.
Modularity as an Organizing Principle
“95% of the words written about software architecture are spent extolling the benefits of modularity, and little, if anything, is said about how to achieve it.”
Modularity is not a feature of a system—it is an organizing principle.
In physical systems, energy must be continuously applied to maintain order. Software systems behave similarly. Without deliberate effort, structural clarity degrades over time. Modularity does not emerge accidentally; it must be maintained.
From an architectural perspective, modularity refers to breaking a system into smaller, related groupings of responsibility.
In frontend systems, these groupings might be:
- Component clusters
- Feature folders
- Hooks and utilities
- State slices
- Pages and layouts
Modularity vs Granularity
Modularity describes whether a system is broken into parts.
Granularity describes how large those parts are.
Moving from a monolithic architecture (for example, a traditional layered system) toward a more distributed style (such as microservices or micro-frontends) increases modularity. However, the size of each module—their granularity—introduces its own trade-offs.
“Embrace modularity, but beware of granularity.”
In frontend terms:
- Too coarse: large feature folders that become mini-monoliths
- Too fine: excessive component fragmentation that increases coordination cost
The tension is structural, not stylistic.
What a Module Means in Practice
A module is a logical grouping of related code.
Depending on language and paradigm, this may look different:
- Classes in object-oriented systems
- Functions in functional systems
- Files and folders in frontend codebases
Throughout architectural discussions, module is used as a general term for related units of behavior and data—not a specific language construct.
In React-based systems, modules often emerge implicitly through:
- Folder structure
- Import boundaries
- State ownership
- Rendering responsibilities
Measuring Modularity
Modularity can be discussed qualitatively, but architecture also offers analytical lenses for evaluation.
Cohesion
Cohesion measures how strongly related the elements inside a module are.
“A cohesive module should not be divided; doing so increases coupling and reduces readability.”
High cohesion implies:
- Clear responsibility
- Minimal unrelated logic
- Predictable change impact
Low cohesion often signals:
- Mixed concerns
- Accidental growth
- Future refactoring pressure
Common Types of Cohesion (from strongest to weakest)
- Functional cohesion
All parts contribute to a single responsibility. - Sequential cohesion
Output of one part becomes input to another. - Communicational cohesion
Parts operate on shared data. - Procedural cohesion
Parts must execute in a specific order. - Temporal cohesion
Parts are related by timing (e.g., initialization). - Logical cohesion
Grouped by category, not behavior. - Coincidental cohesion
Unrelated elements grouped together.
Frontend systems often drift toward temporal or logical cohesion as they grow.
Coupling and Stability
Coupling measures how dependent modules are on one another.
Two commonly discussed forms:
- Afferent coupling – who depends on this module
- Efferent coupling – who this module depends on
High coupling increases fragility. Changes propagate farther and break more easily.
This leads to metrics such as:
- Instability – likelihood a module will change
- Abstractness – proportion of abstract vs concrete elements
These values form the well-known Zones of Pain and Uselessness diagram.
Connascence: A Deeper View of Coupling
Two components are connascent if a change in one requires a change in another to maintain correctness.
Connascence shifts the conversation from dependency count to dependency strength.
Static Connascence (compile-time)
- Name – agreement on identifiers
- Type – agreement on data types
- Meaning (Convention) – agreement on value semantics
- Position – agreement on parameter order
- Algorithm – agreement on implementation logic
Dynamic Connascence (runtime)
- Execution – order of execution matters
- Timing – time sensitivity between components
- Values – values must change together
- Identity – shared reference to the same entity
From an architectural perspective, weaker forms are preferable—especially across module boundaries.
Locality, Distance, and Refactoring
Two guiding observations help improve modularity over time:
- Rule of Degree
Strong forms of connascence can often be refactored into weaker forms. - Rule of Locality
As distance between components increases, coupling should become weaker.
In modern terms, this aligns closely with bounded contexts from Domain-Driven Design: keep high coupling local, and reduce assumptions across boundaries.
Frontend systems naturally grow. Without attention, small issues scale disproportionately.
Modularity as an Ongoing Effort
Improving modularity generally involves:
- Minimizing overall connascence
- Containing strong coupling within narrow boundaries
- Maximizing internal cohesion
These are not one-time activities. They are continuous structural adjustments as systems evolve.
Modularity is not about perfection—it is about maintaining changeability.
Closing Perspective
Modularity is less about decomposition and more about containment.
It reflects how well a system:
- Absorbs change
- Limits ripple effects
- Preserves clarity over time
In frontend architecture, modularity determines whether a codebase remains a product—or slowly becomes an obstacle.
