skip to content

Connascence (Meilir Page-Jones) is a finer-grained model of coupling than the classic content-to-data scale. What are its static and dynamic forms, and what three properties does it tell you to reduce?

level: seniorimportance: nice to knowfreq 18%

answer

  1. Connascent = must change together
  2. Static: Name, Type, Meaning, Position, Algorithm
  3. Dynamic: Execution order, Timing, Value, Identity
  4. Reduce strength, degree, locality-distance
  5. Strong is OK if local

basics

~20 s

Connascence says two pieces of code are connascent if changing one requires changing the other. Static forms (visible in the source): name, type, meaning, position, algorithm. Dynamic forms (only at runtime): execution order, timing, value, identity. Reduce its strength, degree and locality-distance.

solid answer

~50 s

Two components are **connascent** if a change to one requires a matching change to the other to keep the system correct. **Static** forms, weakest to strongest: connascence of **name** (both must agree on an identifier), **type**, **meaning** (a magic value like `status == 2`), **position** (argument or tuple order), **algorithm** (both must implement the same rule, e.g. a checksum or hash). **Dynamic** forms, always stronger because they are invisible in the source: connascence of **execution order** (`open` before `read`), **timing** (a race), **value** (two values must change together, e.g. an invariant across fields), **identity** (both must reference the same instance). Three rules: (1) reduce **strength** — refactor stronger forms into weaker ones, e.g. replace positional arguments with named parameters, replace a magic number with a named constant; (2) reduce **degree** — the number of components involved; (3) reduce **locality-distance** — strong connascence is acceptable inside one small, cohesive module, but across module or service boundaries only the weakest forms are tolerable.

code

pseudocode · 10 lines
pseudocode
// Connascence of POSITION + MEANING: order matters, 2 is a magic value
createUser("ada", "lovelace", 2)

// Demoted to connascence of NAME + TYPE: compiler/reader catches mistakes
createUser(firstName: "ada", lastName: "lovelace", status: Status.SUSPENDED)

// Connascence of ALGORITHM: two hand-written copies of one rule
client:  signature = hash(body + secret)
server:  expected  = hash(secret + body)   // silently mismatched
// Fix: one shared, versioned signing component used by both sides

go deeper

for a junior

Give the definition — two things are connascent if changing one forces changing the other — and one concrete example, such as argument order or a magic number.

for a middle

List the static forms (name, type, meaning, position, algorithm) and name at least two dynamic ones, with a refactoring that weakens each.

for a senior

Cover all nine forms, the static/dynamic split and why dynamic is stronger, and the three levers: strength, degree, locality — including that strong connascence is acceptable when local.

for a principal

Apply it across service boundaries: connascence of algorithm between client and server, value connascence across replicated data, timing connascence in async workflows, and the trade-off that removing connascence of algorithm creates a shared-library versioning dependency.

## The core idea Meilir Page-Jones introduced connascence to give coupling a vocabulary precise enough to argue about specific refactorings. The definition: > Two software components are **connascent** if a change in one would require the other to be changed in order to maintain overall correctness. That reframes coupling from "how do these modules talk" to **"what must change together"** — which is the thing that actually costs money. ## Static connascence (visible by reading the source) Ordered weakest → strongest: 1. **Connascence of Name (CoN)** — both must agree on a name. Calling `calculateTotal()` means a rename must happen in both places. Unavoidable and the weakest form; safe because refactoring tools and compilers catch it. 2. **Connascence of Type (CoT)** — both must agree on a type. Also usually compiler-checked. 3. **Connascence of Meaning / Convention (CoM)** — both must agree on the *interpretation* of a value: `status == 2` means suspended; `-1` means "not found"; an empty string means "unset". Nothing checks it. *Fix:* replace magic values with named constants or an enumerated type, demoting CoM to CoN/CoT. 4. **Connascence of Position (CoP)** — both must agree on *order*: positional arguments, tuple element order, CSV column order. Swapping two same-typed parameters compiles fine and is wrong at runtime. *Fix:* named/keyword arguments, a parameter object, or distinct value types (`Meters`, `Feet`) so the compiler catches transposition. 5. **Connascence of Algorithm (CoA)** — both must implement the same algorithm: client and server computing the same signature, hash, checksum, or date-bucketing rule; a serialiser and deserialiser hand-written on both sides. *Fix:* extract the algorithm into one shared, versioned component so there is exactly one implementation. ## Dynamic connascence (only observable at runtime) All dynamic forms are considered stronger than all static ones, because no tool can find them by reading code: 6. **Connascence of Execution order (CoE)** — `open()` must precede `read()`; `beginTransaction` before `commit`. *Fix:* make illegal sequences unrepresentable — builders that return the next legal type, scope-bound resource blocks, a single method that performs the sequence. 7. **Connascence of Timing (CoTiming)** — correctness depends on *when* things run: a race condition, a sleep-based test, a cache TTL that must exceed a retry window. *Fix:* explicit synchronisation, idempotency, waiting on conditions rather than durations. 8. **Connascence of Value (CoV)** — several values must change together to preserve an invariant: the three fields of a date, `total` and the sum of `lineItems`, replicated data across two services. *Fix:* encapsulate the invariant behind one type or one transactional boundary (this is exactly what a DDD aggregate does). 9. **Connascence of Identity (CoI)** — components must reference the *same instance*, not merely equal values: two threads must use the same lock object; two objects must share the same queue. Strongest form; *fix:* pass the shared instance explicitly and narrow its lifetime, or eliminate shared mutable identity entirely. (Some presentations also list **contranascence** — the requirement that two things must *differ*, e.g. no two classes in a namespace may share a name — as a related concept.) ## The three rules 1. **Minimise overall connascence** by decomposing into encapsulated modules. 2. **Minimise connascence that crosses boundaries** — locality. *Strong connascence is fine when it is local.* Two lines in the same five-line function may share execution-order connascence harmlessly; the same coupling between two microservices is a production incident waiting to happen. This is connascence's most useful insight and the one the old coupling scale lacks. 3. **Maximise connascence within a boundary** — corollary of the above and effectively a cohesion statement: things that must change together should live together. And for any individual instance you find, the levers are: - **Strength** — how hard it is to detect and refactor. Convert stronger to weaker (position → name, meaning → type). - **Degree** — how many components are entangled. One caller depending on argument order is minor; four hundred is a migration project. - **Locality** — how far apart the entangled components are. Distance amplifies strength. ## Why prefer this over the classic scale The content→data scale tells you a call is "control coupled"; connascence tells you *what will break, whether a tool will catch it, and which refactoring demotes it*. It also covers cases the 1970s scale never anticipated: shared algorithms between a browser and a server, ordering constraints in async workflows, replicated data in distributed systems. ## Edge cases and criticism - **Not all connascence should be removed.** Every working system has connascence of name; it is the price of communication. The goal is weak, low-degree, local connascence. - **Weakening can cost.** Replacing connascence of algorithm with a shared library introduces a new deployment/versioning dependency — sometimes a worse trade than duplicating a trivial rule. - **Dynamic forms dominate in distributed systems.** Execution order, timing and value connascence across services are where real outages come from, and static analysis will never show them; they need contract tests, idempotency and explicit sagas. - **The ranking is a heuristic**, not a total order everyone agrees on; use it to guide discussion, not as a scoring rubric.

  • Why is dynamic connascence always ranked stronger than static?
    Because static forms are visible in the source and often enforced by the compiler or IDE refactoring, so a mistake is caught early. Dynamic forms — order, timing, value, identity — only manifest while running, frequently under load or interleaving, so they surface as intermittent production bugs far from their cause.
  • Give a refactoring that demotes connascence of position.
    Replace positional arguments with named/keyword arguments or a parameter object, or introduce distinct value types (`Celsius`, `Fahrenheit`) so a transposition becomes a compile error. That converts connascence of position into connascence of name or type.
  • How does connascence's locality rule change how you review code?
    You stop flagging coupling in the abstract and start asking how far it reaches. Strong connascence inside one small cohesive function is acceptable and often clearer than an abstraction; the same connascence spanning packages, repositories or services is the thing to fix first.

saying these in an interview costs you the question

  • Claiming all connascence should be eliminated — connascence of name is unavoidable and harmless.
  • Ignoring locality and flagging strong coupling inside a single tiny function as urgently as coupling across services.
  • Ranking dynamic forms below static ones; dynamic forms are stronger precisely because no tool can see them.
  • Confusing connascence of identity (same instance required) with connascence of value (equal values required).
  • Removing duplicated logic by introducing a shared library without acknowledging the new deployment/versioning coupling it creates.

context