Coupling is often described as having degrees of strength rather than being simply present or absent. Name the classic degrees of coupling from worst to best and explain what distinguishes them.
answer
- Content > common > external > control > stamp > data
- Flag parameter = control coupling
- Whole record for one field = stamp coupling
- Mutable global = common coupling
- Refactor = move one rung down, not delete
basics
~20 sFrom worst to best: content (one element reaches into another's internals), common (shared global state), external (shared external format), control (one passes a flag telling the other what to do), stamp (passes a whole record when only a field is needed), and data (passes just the values needed).
solid answer
~50 sConstantine and Myers ranked coupling on a strength scale, and the ranking is what makes Low Coupling actionable — you rarely delete a dependency, you *downgrade* it. **Content coupling**: A modifies or relies on B's internals (direct field access, reflection, jumping into B's implementation). **Common coupling**: A and B share mutable global state; either can corrupt the other invisibly. **External coupling**: both bind to an externally imposed format, protocol, or device. **Control coupling**: A passes a control flag that dictates B's internal path (`render(item, isSummary=true)`), so A must know B's algorithm. **Stamp coupling**: A passes a whole composite record when B needs one field, so B is coupled to a structure it barely uses. **Data coupling**: A passes exactly the primitive values or immutable value objects B needs — the loosest useful form. Message coupling (calling a method with no data at all) is sometimes listed as loosest. Refactoring for Low Coupling usually means moving one step down this ladder.
code
pseudocode · 9 lines// control coupling: caller drives callee's branching
format(invoice, mode = "SHORT")
// stamp coupling: whole aggregate passed for one field
validate(customer) // only reads customer.postalCode
// data coupling: pass exactly what is needed
formatShort(invoice)
validatePostalCode(customer.postalCode)go deeper
Know that coupling has degrees and be able to name the two ends: passing just the data you need is good; reaching into another class's internals or sharing mutable globals is bad.
Recite the classic ladder in order and give a concrete code example of control and stamp coupling plus the standard refactoring for each.
Use the ladder as a review tool ("move it one rung down"), and add the forms the ladder omits — temporal, semantic, and connascence-style analysis.
Map the ladder onto system boundaries: shared mutable database tables as common coupling, wire formats as external coupling, synchronous request chains as temporal coupling, and argue the economics of which rung is worth paying for.
## Why a scale exists Counting dependency edges is a crude measure. Two classes can both "depend on one other class" and yet have wildly different change costs, because *how* they depend differs. Larry Constantine's structured-design work (later popularised by Glenford Myers, Yourdon, and every software-engineering textbook since) ranks coupling from most to least damaging. GRASP's Low Coupling becomes practical when you use this scale: most real refactorings do not remove a dependency, they **weaken** it by one or two rungs. ## The ladder, worst first ### 1. Content coupling (a.k.a. pathological coupling) One element depends on or modifies the *internal* workings of another: writing another object's private field, relying on its internal layout, patching its code at runtime, or jumping into the middle of its logic. Any internal refactoring of the target silently breaks the dependant. Modern equivalents: reflection into private state, monkey-patching, reading another module's internal data table directly. ### 2. Common coupling (global coupling) Two elements share mutable global data — a global variable, a mutable singleton, a static registry, a shared mutable configuration object, or (at system scale) a shared mutable database table written by two services. Problems: you cannot reason about either element locally, the write order becomes an invisible protocol, and concurrency bugs become likely. Note that sharing an *immutable* global constant is essentially harmless — mutability is what makes common coupling toxic. ### 3. External coupling Both elements are bound to an externally imposed format, protocol, device, or interface — a wire format, a file layout, a hardware register map. It is not always avoidable (you must speak HTTP to speak HTTP), but it should be isolated behind one adapter rather than smeared through the codebase. ### 4. Control coupling A passes B a value that controls B's internal flow rather than describing data: a boolean flag, a mode enum, a "what to do" string. ``` report.render(data, summaryOnly = true) // control coupling ``` The caller now knows about B's internal branches, and adding a third mode forces edits on both sides. The standard cures: split into two intention-revealing methods (`renderSummary()` / `renderFull()`), or replace the flag with **polymorphism** — passing an object that *is* the behaviour rather than a switch selecting it. ### 5. Stamp coupling (data-structured coupling) A passes a whole composite structure when B needs only part of it: handing the entire `Customer` aggregate to a function that only reads `postalCode`. B is now recompiled/retested when unrelated parts of `Customer` change, and B is unusable in contexts where no `Customer` exists. Cure: pass the narrow value, or a purpose-built interface/DTO exposing only what is needed (**interface segregation** applied to parameters). ### 6. Data coupling A passes exactly the simple values or immutable value objects B needs. This is the loosest form that still allows collaboration, and it is the target state for most parameter passing. ### 7. (Sometimes listed) Message coupling / no coupling Interaction via a method call carrying no data, or via messages/events with no shared types at all. Often listed as the loosest rung; it is the conceptual endpoint of the ladder rather than a practical universal goal. ## Edge cases and honest caveats - **The ladder is a heuristic, not a law.** Passing an aggregate root is *stamp coupling* by the letter of the scale but is completely idiomatic in domain-driven designs where the aggregate is the unit of consistency. Judgement beats mechanical rule application. - **Weakening coupling can cost cohesion or performance.** Decomposing an aggregate into eight scalar parameters produces a long, unreadable parameter list — a different smell ("long parameter list", and loss of a meaningful concept). - **Temporal and semantic coupling are not on the classic ladder** but matter more at architecture scale: temporal coupling (A must be called before B; or a synchronous call requiring B to be up right now) and semantic/behavioural coupling (A silently relies on B's undocumented behaviour — the hardest kind to see, since the compiler never mentions it). - **Connascence** (Meilir Page-Jones) is a finer, more modern reformulation of the same idea with explicit strength, degree, and locality axes. ## Using the ladder in practice In code review, do not ask "is this coupled?" — everything is. Ask **"what rung is this on, and can I cheaply move it one rung down?"** Typical one-rung moves: replace a boolean flag with two methods (control → none), replace a passed aggregate with the field actually used (stamp → data), replace a mutable singleton with an injected parameter (common → data), replace direct field access with a published method (content → data).
- Passing a domain aggregate to a service is technically stamp coupling. Is that automatically a design defect?No. If the aggregate is the meaningful unit of consistency and the callee genuinely operates on that concept, passing it is clearer than exploding it into scalars. Mechanically shredding parameters trades stamp coupling for a long-parameter-list smell and loses a domain concept. The rung tells you where to *look*, not what to *do*.
- A boolean flag parameter is control coupling — what are the two standard cures?Split the method into two intention-revealing methods (one per branch), or replace the flag with polymorphism by passing an object that encapsulates the behaviour. Both remove the caller's knowledge of the callee's internal branching.
- Which forms of coupling does a compiler fail to warn you about?Semantic/behavioural coupling (relying on undocumented behaviour or ordering), temporal coupling (call A before B), and common coupling through mutable global state. These are invisible to type checking, which is exactly why they cause the worst production surprises.
Ordering coffee. Content coupling: you walk behind the counter and work the machine yourself. Common coupling: you and the barista both scribble on one shared notepad anyone can edit. Control coupling: you say "use procedure 3" instead of what you want. Stamp coupling: you hand over your entire wallet so they can take the price. Data coupling: you say "a flat white" and hand over the exact amount.
saying these in an interview costs you the question
- Treating any dependency as equally costly instead of ranking its strength.
- Calling a shared *immutable* constant "common coupling" — mutability is the harmful part.
- Mechanically shredding every aggregate parameter into scalars, creating long parameter lists and losing domain concepts.
- Claiming a boolean flag parameter is fine "because it's just one parameter" — the cost is the caller knowing the callee's control flow, not the parameter count.
- Ignoring temporal and semantic coupling because the type system does not surface them.