skip to content

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.

level: middleimportance: should knowfreq 52%

answer

  1. Co-Lo-Te-Pro-Com-Se-Fun (worst→best)
  2. Coincidental = Utils grab bag
  3. Logical = flag/switch picks the branch
  4. Sequential = output feeds next input
  5. Functional = name it without "and"

basics

~10 s

From 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 s

The 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
pseudocode
// 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

for a junior

Contrast the two ends: a Utils grab bag (coincidental) versus a module that does one named job (functional), and say higher is better.

for a middle

List the levels in order with a one-line example each, and identify the refactoring implied by each low level (flag → polymorphism, phase → delegation).

for a senior

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.

for a principal

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.

context