The classic structured-design cohesion scale ranks a module's internal relatedness from coincidental up to functional. Name the levels in order and give an example of the worst and the best.
answer
- Co-Lo-Te-Pro-Com-Se-Fun (worst→best)
- Coincidental = Utils grab bag
- Logical = flag/switch picks the branch
- Sequential = output feeds next input
- Functional = name it without "and"
basics
~10 sFrom worst to best: coincidental, logical, temporal, procedural, communicational, sequential, functional. Coincidental is a Utils class of unrelated helpers. Functional is a module that does exactly one well-defined job, like calculating sales tax.
solid answer
~50 sThe seven classic levels, worst to best: **coincidental** (elements grouped for no reason — a `Utils` dumping ground); **logical** (same category of action, selected by a flag — one `handleIO(mode)` doing disk, network and console); **temporal** (things that happen at the same time — a `startup()` that opens a DB, loads config and warms a cache); **procedural** (steps that must run in a set order but share no data); **communicational** (steps that operate on the same data — read and validate the same record); **sequential** (output of one step is input to the next — parse, then transform); **functional** (everything contributes to one single well-defined task — `calculateSalesTax`). Only functional cohesion gives a module a name with no "and" in it, one reason to change, and clean reusability. The scale matters less as a recitation than as a diagnostic: naming where a module sits tells you what refactoring it needs — split a logical module by its flag, split a temporal one by concern.
code
pseudocode · 12 lines// LOGICAL cohesion: one module, behaviour picked by a flag
function report(kind, data) {
if (kind == "PDF") { ...pdf layout... }
else if (kind == "CSV") { ...csv escaping... }
else if (kind == "HTML"){ ...html markup... }
}
// FUNCTIONAL cohesion: one job per module, chosen by type
interface Reporter { render(data): Bytes }
module PdfReporter implements Reporter { render(d) { ...pdf layout... } }
module CsvReporter implements Reporter { render(d) { ...csv escaping... } }
module HtmlReporter implements Reporter { render(d) { ...html markup... } }go deeper
Contrast the two ends: a Utils grab bag (coincidental) versus a module that does one named job (functional), and say higher is better.
List the levels in order with a one-line example each, and identify the refactoring implied by each low level (flag → polymorphism, phase → delegation).
Use the scale diagnostically on real code, note that thin orchestration layers may legitimately sit low, and connect it to SRP, LCOM metrics and change-locality.
Discuss cohesion at module/service scale: boundaries aligned with domain concepts and team ownership, the risk of over-splitting into shotgun surgery, and cohesion defined by axis of change rather than surface similarity.
## What the scale is Cohesion answers: *why are these elements in the same module?* Constantine, Myers and Stevens ranked the possible answers into seven levels. The ranking is by how well the grouping survives change: the higher the level, the more likely that a change to one element is a change to the module's single purpose, and the less likely that unrelated stakeholders both need to edit it. Read the list bottom-up as "the reason these things are together": ### 1. Coincidental (worst) — "no reason at all" Elements are together by accident or convenience. Classic example: `Utils`, `Helpers`, `Common`, `Misc` — a string trimmer, a date parser and a retry loop in one file. Symptoms: everyone imports it, everyone edits it, it has no coherent test suite. Fix: distribute each helper to the module that owns the concept, or create small named modules (`DateFormatting`). ### 2. Logical — "same *category* of thing, chosen by a parameter" One module handles several related-in-kind operations, selected by a flag or switch: `handleInput(source)` doing keyboard, file and network input; `report(type)` doing PDF, CSV and HTML. Symptom: a big `switch` at the top and branches sharing almost no code. Fix: separate implementations behind a common interface (polymorphism instead of a flag) — this is the classic "replace conditional with polymorphism" refactoring. ### 3. Temporal — "they run at the same time" Grouped because they occur in the same phase, not because they are related: `initialize()` opening a DB connection, seeding caches, registering signal handlers, and starting a metrics reporter. Symptom: the module changes whenever *any* subsystem changes its startup needs. Fix: let each subsystem own its own init and have the phase orchestrate calls to them. Note that temporal cohesion is often *acceptable* in a thin orchestration layer — the smell is when logic lives there rather than delegation. ### 4. Procedural — "they must run in this order" Elements are sequenced by control flow but do not share data: check permissions, then write an audit line, then send an email. Better than temporal because the order is meaningful, but the parts are still independent concerns. ### 5. Communicational (also called *informational*) — "they touch the same data" All operations act on the same input/record: validate the order, price the order, format the order. This is the level most well-designed classes and repositories reach — everything in `CustomerRecord` operates on the customer's data. ### 6. Sequential — "one's output is the next's input" A pipeline: tokenize → parse → typecheck. Data flows through the module in one direction, so the parts genuinely belong together as one transformation. ### 7. Functional (best) — "everything serves one task" Every element contributes to a single, well-defined job that can be stated in a sentence with no conjunction: `calculateSalesTax(order, jurisdiction)`, `EncodeJpeg`. Such a module has one reason to change, is trivially reusable, and its tests read as a specification of the one task. ## Two later additions you may hear - **Object cohesion / informational cohesion** — modern OO extension of communicational cohesion: a class is cohesive when its methods use its fields. This is what the LCOM (Lack of Cohesion of Methods) family of metrics tries to measure: if methods partition cleanly into groups that touch disjoint field sets, the class is really two classes. - **Semantic / conceptual cohesion** — do the identifiers and comments talk about one domain concept? Measured by text-similarity metrics in research tools. ## Using the scale in practice You will rarely be asked to recite it. Its value is diagnostic — the level tells you the refactoring: | Level found | Typical fix | |---|---| | Coincidental | Move each element to its owning module | | Logical | Replace the flag with polymorphism / separate types | | Temporal | Delegate to each subsystem; keep only orchestration | | Procedural | Extract each step; consider whether the sequence is a workflow object | | Communicational | Usually fine — check the data really is one concept | | Sequential | Fine — consider making the pipeline explicit | | Functional | Target state | ## Edge cases - **Higher is not always mandatory.** A composition-root / bootstrap module is legitimately temporal; a facade is legitimately procedural. The rule is that *low-level business logic* should be functional, while thin coordination layers may sit lower on the scale by design. - **Cohesion is measured against a purpose, not against surface similarity.** Two functions that both call the same library are not thereby cohesive. Conversely, code that looks different may be cohesive if it all serves one business rule. - **Over-splitting kills cohesion too.** Decomposing until every module has one function can scatter a single concept across many files, producing shotgun surgery. Cohesion is about one *concept*, not one *statement*.
- Is temporal cohesion always a defect?No. A bootstrap or composition-root module is legitimately organised by phase. It becomes a defect when real logic lives there instead of being delegated to the subsystems that own it, because then the module changes for every unrelated reason.
- How would you measure cohesion automatically?The LCOM family (Lack of Cohesion of Methods) partitions a class's methods by which fields they access; disjoint partitions suggest the class should be split. It is a heuristic only — it misses semantic cohesion and penalises legitimate data-free helper methods, so treat it as a hint for review, not a gate.
- How does this scale relate to the Single Responsibility Principle?SRP is roughly a demand for functional cohesion, phrased in terms of change: one reason to change, one group of stakeholders. The scale is the finer-grained diagnostic version of the same idea.
Imagine drawers in a kitchen. Coincidental: a junk drawer of batteries, receipts and string. Logical: 'things you cut with' — bread knife, scissors, secateurs — related in kind but used for utterly different jobs. Temporal: 'stuff you grab at breakfast'. Functional: the drawer that holds only the parts of the coffee grinder.
saying these in an interview costs you the question
- Calling a module cohesive because it is small — size is not relatedness.
- Believing every module must reach functional cohesion; bootstrap and orchestration layers are legitimately temporal or procedural.
- Confusing logical cohesion (a flag picks the branch) with functional cohesion because there is one entry point.
- Treating a `Utils` or `Helpers` class as harmless organisation rather than coincidental cohesion.
- Judging cohesion by surface similarity of code instead of by shared purpose and shared reason to change.