Software design literature ranks cohesion on a scale from coincidental to functional. Name the main levels of that scale and give an example of a class sitting at a weak level versus a strong one.
answer
- Worst→best: coincidental, logical, temporal, procedural, communicational, sequential, functional
- Coincidental = Utils bucket; logical = switch on a type flag
- Temporal = 'happens at startup'; procedural = 'happens in this order'
- Communicational = same data; sequential = output feeds input
- Data-cohesive can still be change-incohesive → follow reason to change
basics
~20 sFrom worst to best: coincidental (random stuff together), logical (same category), temporal (runs at the same time), procedural/communicational (same sequence or same data), sequential (output feeds the next step), and functional (everything serves one single task). Aim for functional.
solid answer
~50 sThe classic Constantine/Yourdon scale, ordered worst to best, is: **coincidental** (members grouped for no reason — a `Utils` bucket); **logical** (same broad category, selected by a flag — one `handleInput(type)` doing keyboard, mouse and file input); **temporal** (grouped because they happen at the same moment — an `initialize()` or `shutdown()` class); **procedural** (executed in a fixed order but on unrelated data); **communicational/informational** (all operate on the same data — a class over one record); **sequential** (each member's output is the next one's input — a parsing pipeline); **functional** (every member contributes to one well-defined task — a `TaxCalculator`). Modern OO adds a practical rule of thumb: group by *reason to change*, not by data type alone. Functional and sequential are the targets; coincidental, logical and temporal cohesion are the ones worth refactoring away, usually via Extract Class.
go deeper
Name the two ends of the scale — coincidental (Utils bucket) as worst, functional (one task) as best — and give one example of each.
List the full ordering with a one-line example per level and diagnose a given class, then name the refactoring that raises it.
Explain why weak levels create accidental coupling and shotgun surgery, and add the modern caveat that reason-to-change beats data-relatedness.
Map the same scale onto packages, services and team boundaries; discuss when logical or temporal grouping is a deliberate, bounded compromise (bootstrap, closed variant sets, framework wiring).
## The scale Cohesion was first ranked as an ordinal scale in structured design (Larry Constantine, later Yourdon & Constantine, then Stevens/Myers). It was written for procedural modules but transfers cleanly to classes, packages, and services. From **weakest to strongest**: | Level | Members are grouped because… | Typical smell | |---|---|---| | **Coincidental** | no reason at all | `Utils`, `Helpers`, `Common`, `Misc` | | **Logical** | they fall in the same broad category, and a flag/parameter picks one | `handle(type)` with a giant switch; `IOHelper` doing disk, network and keyboard | | **Temporal** | they run at the same *time* | `Startup`, `Shutdown`, `onAppLaunch` doing ten unrelated inits | | **Procedural** | they run in a fixed *order* | `processMonthEnd()` calling unrelated steps in sequence | | **Communicational** (a.k.a. informational) | they act on the same *data* | a class holding every operation over one record | | **Sequential** | one member's *output* is the next one's *input* | tokenize → parse → evaluate | | **Functional** | they all serve **one single, well-defined task** | `TaxCalculator`, `PasswordHasher`, `CsvRowParser` | Some authors insert **procedural** and **communicational** in the other order, or add a top level for objects that also encapsulate their own state (**object/informational cohesion**). Do not fight over the exact ordering in an interview — what matters is that you can (a) name the weak end, (b) name the strong end, (c) diagnose a real class. ## Why the weak levels are weak — the mechanism - **Coincidental:** nothing predicts what is inside, so nothing predicts where a new thing goes. The bucket grows without bound, and every consumer depends on the whole bucket, creating wide accidental coupling. - **Logical:** the giant `switch`/`if` on a type flag means every new variant edits the same function — the exact shape the Open/Closed Principle and GRASP *Polymorphism* exist to remove. Callers must also pass a magic flag, so the interface leaks the branching. - **Temporal:** "happens at startup" is not a design relationship. When the timing changes (lazy init, feature flags, a new deployment model) the grouping evaporates and you must untangle unrelated code that has meanwhile grown shared state. - **Procedural:** ordering is a fragile reason for togetherness — reorder the workflow and the class's justification disappears. Also common in "transaction script" style code where domain logic drains out of the entities. ## Why the strong levels are strong - **Communicational:** everything works on one data structure, so encapsulation is natural — this is basically what an OO class *is*, and it aligns nicely with GRASP **Information Expert** (give a responsibility to the class that holds the data needed to fulfil it). - **Sequential:** the pipeline shape gives an obvious ordering and obvious seams for testing each stage. - **Functional:** the class is trivially nameable, trivially reusable, trivially testable, and has one reason to change. This is the target and it lines up exactly with the Single Responsibility Principle. ## The modern caveat: data-relatedness is not enough A class can be at communicational level (all methods touch the same record) and still be bad: a 60-method `User` class touching `user` for authentication, billing, GDPR export and marketing preferences is data-cohesive but has four unrelated *reasons to change* and four different stakeholder groups. That is why the SRP restatement — "gather things that change for the same reason and for the same actor; separate things that change for different reasons" — is the more useful modern criterion. Data cohesion and change cohesion usually agree; when they disagree, follow change. ## How to move up the scale - Coincidental/logical → extract each concern into its own type; replace the type flag with polymorphism (a strategy per variant). - Temporal → keep an `initialize()` orchestrator if you must, but have it *delegate* one line per subsystem, each subsystem owning its own init. - Procedural → push each step's logic into the object that owns the corresponding data (Information Expert), leaving a thin coordinator. - Communicational → split by reason-to-change/actor when the class grows several unrelated method clusters. ## Edge cases - **Temporal cohesion is sometimes correct**: a lifecycle hook, a migration script, an application bootstrap that only delegates. Judged by whether it *contains* logic or merely *sequences* it. - **Logical cohesion is acceptable for stable, closed sets** — a fixed enum of three currencies is not going to grow; the polymorphic refactor may cost more than it saves. - **A genuine stateless utility namespace** (pure math functions) is fine: `MathUtils` full of trigonometry is functionally cohesive around a topic; `Utils` full of string, date, HTTP and file helpers is coincidental. The name tells you which one you have.
- Which refactoring moves a class from logical cohesion up the scale?Replace Conditional with Polymorphism, followed by Extract Class: each branch of the type switch becomes its own class implementing a shared interface, so adding a variant means adding a class instead of editing a shared function.
- Is temporal cohesion always a defect?No. A bootstrap or lifecycle hook legitimately groups by timing, provided it only *delegates* — one line per subsystem — and holds no logic or shared state of its own. It becomes a defect when the unrelated initializations grow bodies and start sharing variables.
saying these in an interview costs you the question
- Claiming coincidental cohesion is the best because 'everything is in one place'
- Describing 'temporal' as strong because 'the steps are related in time'
- Insisting a class is cohesive purely because all methods touch the same data, ignoring unrelated reasons to change
- Arguing that a giant switch on a type code is fine because it is 'centralized'