Cohesion
Cohesion measures module focus: do its parts change for the same reason? Functional cohesion is the goal; coincidental is an accident. LCOM signals disconnected clusters. Cohesion is the counterweight that stops over-decoupling from fragmenting a module.
A billing engineer at a B2B SaaS platform was trying to understand the BillingUtils class. It contained: formatCurrency(), parseInvoiceXML(), sendEmailNotification(), retryWithBackoff(), calculateTax(), validateIBAN(), and archiveOldInvoices(). The class had grown over two years as developers dropped “billing-related” utility functions into the most convenient container. Every function worked. None of them belonged together in any meaningful sense — they shared a folder, not a reason to change. When the payment processor changed their API and the engineer needed to find all the places that formatted currency, they had to read the entire class to be sure they had found them all. The class had no cohesion, and its lack of cohesion made it impossible to reason about independently.
The cohesion spectrum
Yourdon and Constantine defined cohesion on a spectrum from coincidental (lowest) to functional (highest), just as they defined the coupling ladder. The levels describe why the elements in a module are grouped together.
Coincidental cohesion (lowest): elements are grouped for no structural reason — they happen to be in the same file or class. BillingUtils in the opening story is coincidentally cohesive. The only thing that connects its functions is that a developer decided to put them there. Any refactoring requires reading the entire module to understand what is in it.
Logical cohesion: elements are grouped because they are logically similar — all error handlers, all string formatters, all validation functions. A StringUtils class with trim, truncate, toSlug, and escapeHtml has logical cohesion. They share a general domain (strings), but they do not necessarily change together or serve the same purpose.
Temporal cohesion: elements are grouped because they execute at the same time — all startup initialization logic, all cleanup-on-shutdown logic. The functions run together but may have nothing else in common.
Communicational cohesion: elements are grouped because they operate on the same data. All functions that operate on an Order object form a communicationally cohesive group — but a single Order object with dozens of methods for completely different concerns (pricing, shipping, fulfillment, billing, audit) is still weakly cohesive if those concerns change for unrelated reasons.
Functional cohesion (highest): all elements contribute to a single, well-defined purpose. The module does one thing, all its parts serve that one thing, and a change to that one thing requires changing only this module. The InvoiceGenerator that accepts a confirmed order and produces a formatted invoice with correct tax and totals — and does nothing else — has functional cohesion. When the invoice format changes, you change this module. When tax law changes, you change this module’s tax calculation. When the payment status changes, you do not touch this module at all.
LCOM: a structural intuition for cohesion
Lack of Cohesion of Methods (LCOM) is a static metric that gives a structural sense of how well a class’s methods belong together. The intuition is simple: if a class has methods A, B, and C, and methods A and B share instance variables but method C shares no variables with either, the class is likely doing two things. Method C probably belongs elsewhere.
The most useful form (LCOM4, or the variant in common static analysis tools) counts the number of connected components in a graph where nodes are methods and edges connect methods that share at least one instance variable. LCOM = 1 is ideal: every method is reachable from every other method via shared variables — the whole class is one tightly connected thing. LCOM > 1 means the class has multiple independent clusters that likely should be separate modules.
In the B2B platform, imagine a BillingProcessor class with these method/field relationships:
generateInvoice()andcalculateTax()both useorderItemsandcustomerIdsendEmailNotification()andformatEmailBody()both useemailAddressandemailTemplate- No method in the first group shares any field with the second group
LCOM = 2. The class is doing two unrelated things: invoice generation and email sending. They should be two classes.
▸lesson.inset.note
LCOM is a diagnostic, not a decree. A class with LCOM = 2 might be intentionally grouping two things that always change together for business reasons — that is a design choice, not a defect. Use LCOM to identify candidates for review, not to trigger automatic refactoring. The question it asks is: “do these parts actually belong together?” — the answer requires human judgment about the change boundaries of the domain.
Cohesion as the counterweight to over-decoupling
The previous lesson established that coupling should be minimized — specifically, the degree to which one module depends on the internals of another. But taken to its extreme, “minimize coupling” produces modules so fine-grained that no module has any content of its own. You end up with fifty classes each containing one method. Every operation requires composing fifteen classes. The code has data coupling everywhere and is completely unreadable.
Cohesion is the counterforce. A highly cohesive module keeps things together that change together — which is exactly what defines a good structural boundary. When everything in a module changes for the same reason, the module is a stable unit of evolution. When parts of a module change for different reasons, the module is the wrong boundary.
The practical relationship: high cohesion naturally reduces the coupling surface. A highly cohesive InvoiceGenerator exposes a clean, minimal interface (data coupling from the outside) because everything it needs internally is already encapsulated. You do not need to reach into its internals because all its internals are coordinated toward one purpose. Low cohesion, by contrast, forces callers to know about the module’s internal structure — because the module has no structure of its own to rely on.
▸Why this works
Why does cohesion matter more than it appears? Because the cost of low cohesion is paid at the wrong time. The BillingUtils class from the opening story works perfectly. Every function returns the correct result. The cost appears only when you need to change something: you cannot determine what is in the module without reading all of it, you cannot test a subset of it without understanding the rest, and you cannot evolve one concern without risking accidental collision with another. Low cohesion is invisible debt — it compounds at change time, not at write time.
A module contains three functions: `processPayment()`, `logPaymentAttempt()`, and `notifyFinanceTeam()`. Each function is triggered at payment processing time and each modifies the same payment record. Which form of cohesion best describes this module?
Static analysis shows that the BillingProcessor class has LCOM = 3. Methods that generate invoices share fields with each other, methods that send notifications share fields with each other, and methods that archive old records share fields with each other — but no fields are shared across the three groups. What does this tell you, and what should you do?
A team splits the InvoiceGenerator class into twelve single-method classes to achieve maximum loose coupling. Now generating an invoice requires coordinating twelve calls in the right order. What structural problem did they create?
- 01What is functional cohesion, and how does it differ from communicational cohesion?
- 02What does LCOM measure, and what does LCOM > 1 tell you?
- 03Why is 'maximize decoupling everywhere' a trap, and what corrects it?
Cohesion measures how focused a module is — whether its parts belong together because they serve a single purpose and change for a single reason. The cohesion spectrum runs from coincidental (arbitrary grouping, the weakest form) through logical, temporal, and communicational cohesion up to functional cohesion (all parts serve one purpose, all change for one reason — the strongest form and the goal).
LCOM provides a structural signal: a class with multiple disconnected method clusters (LCOM > 1) is doing multiple things. It is a diagnostic tool, not a mandate — the question is always whether the clusters should be separated based on whether they have independent reasons to change.
Cohesion is the counterweight to the decoupling pressure from the previous lesson. Minimizing pairwise coupling without respecting cohesion produces over-decomposed modules where coordination logic distributes into every caller. The correct structural boundary is the natural change boundary: things that change together for the same reason belong together, and that boundary naturally produces a clean, minimal interface to the outside (data coupling) because everything needed internally is already encapsulated.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.