Meilir Page-Jones proposed "connascence" as a finer-grained replacement for the coupling/cohesion vocabulary. What is it, what are its three measures, and how does it guide refactoring decisions across a module boundary?
answer
- Born together: change one, must change the other
- Static: name, type, meaning, position, algorithm
- Dynamic: execution order, timing, value, identity
- Measures: strength, degree, locality
- Weaken what crosses the boundary; keep strong forms local
basics
~20 sConnascence means two pieces of code are "born together": if one changes, the other must change to stay correct. It names the kind of agreement (name, type, position, meaning, algorithm, timing, order, identity) and rates it by strength, degree, and locality — giving concrete refactoring targets instead of a vague "too coupled".
solid answer
~60 sConnascence exists between two elements when a change in one requires a corresponding change in the other for the program to remain correct. Page-Jones splits it into **static** forms detectable by reading code — name, type, meaning (magic values), position (argument/field order), algorithm (both sides implement the same encoding) — and **dynamic** forms only observable at runtime — execution order, timing, values (linked invariants), identity (must reference the same instance). It adds three orthogonal measures: **strength** (how hard it is to detect and fix — name is weakest, algorithm/identity strongest), **degree** (how many elements share the agreement — two callers vs. two hundred), and **locality** (how far apart they are — same function vs. across services). The rules of thumb: minimize overall connascence; convert strong forms to weaker ones (positional args → named parameters; magic number → named constant; shared algorithm → shared library or explicit schema); and keep whatever remains **local** — strong connascence inside one small module is fine, weak connascence across a service boundary may not be. That gives you an argument for *where* to draw the boundary, not just a complaint.
code
pseudocode · 14 lines// Connascence of Meaning (magic value) + Position (argument order)
sendReport("2026-08", 3, true) // what is 3? what is true?
// weakened to Connascence of Name
sendReport(period = "2026-08", status = Status.SHIPPED, includeDrafts = true)
// Connascence of Algorithm across a boundary: both sides hash independently
service A: token = sha256(user + secret + timestamp)
service B: expect sha256(user + secret + timestamp) // silent break if either changes
// weakened: one shared, versioned signing library + conformance tests
// Connascence of Execution order -> make illegal states unrepresentable
val conn = Connector.open(cfg) // returns Connection only after opening
conn.read() // read() cannot exist before open()go deeper
Know the one-line definition — if one element changes, the other must change too — and one example, such as a magic number that two places must interpret the same way.
Name several forms (name, type, meaning, position, algorithm, execution order, timing, value, identity) and give the standard weakening moves: magic value → named constant, positional args → named parameters or a parameter object.
Use strength/degree/locality together and apply the heuristics — minimize crossing-boundary connascence, weaken what must cross — with concrete refactors, and connect it back to the classic coupling ladder it refines.
Use it as a boundary-placement instrument: audit what crosses each candidate line, refuse splits across strong dynamic forms, mandate explicit versioned schemas and shared/conformance-tested implementations where degree is high, and be explicit that it's a reasoning lens without real tooling, to be paired with dependency-direction and cycle analysis.
## Origin and the core idea Meilir Page-Jones introduced connascence in *What Every Programmer Should Know About Object-Oriented Design* (1996), extending Constantine's coupling work into something precise enough to reason about mechanically. The word means "having been born together". **Definition:** two software elements are connascent if a change to one requires a corresponding change to the other in order for the overall system to remain correct. That framing beats "coupling is bad" in three ways: it is *directional about change* (the thing that actually costs money), it *names* the kind of agreement so you can look up the standard remedy, and it applies uniformly from two lines of one function up to two services in different data centers. ## Static forms (visible by reading the code) Roughly ordered weakest → strongest: 1. **Connascence of Name (CoN).** Both elements must agree on a name — a caller uses `calculateTax`. Weakest and unavoidable; automated rename refactoring handles it. 2. **Connascence of Type (CoT).** Both must agree on a type. Also cheap; compilers enforce it. 3. **Connascence of Meaning / Convention (CoM).** Both must agree on the *interpretation* of a value: `status == 3` means shipped; `-1` means "not found"; an empty string means "unset". Nothing enforces it. **Remedy:** replace magic values with named constants or enums — converting CoM into CoN. 4. **Connascence of Position (CoP).** Both must agree on ordering: positional arguments (`createUser("Smith", "John")` — which is first?), tuple element order, CSV column order, byte offsets in a binary frame. **Remedy:** named parameters, a parameter object, or a named-field schema — converting CoP into CoN. 5. **Connascence of Algorithm (CoA).** Both sides must implement the *same algorithm* to interoperate: two services independently hashing a password, both sides computing a checksum, client and server each implementing the same serialization or signature scheme. Strongest static form, because a divergence is silent and the compiler is no help. **Remedy:** one shared implementation (library or service) that both call, or a formal specification plus cross-implementation conformance tests. ## Dynamic forms (only observable at runtime) All considered stronger than the static forms, because tooling can rarely find them: 6. **Connascence of Execution order (CoE).** Operations must happen in a given order: `open()` before `read()`, `beginTransaction()` before `commit()`. **Remedy:** make illegal states unrepresentable — return the next-stage object from the previous stage (a builder that only yields a `Connection` after `open`), or a type-state/session-type style API. 7. **Connascence of Timing (CoTi).** Correctness depends on *when* — a sleep-based wait, a race between threads, a cache TTL that must exceed a downstream retry budget. **Remedy:** explicit synchronization, idempotency, and event/completion signals instead of timing assumptions. 8. **Connascence of Value (CoV).** Several values must change together to preserve an invariant: a rectangle's corner coordinates, a denormalized total and its line items, a replicated config in two places. **Remedy:** encapsulate the invariant behind one owner so a single operation changes all of it atomically; derive rather than duplicate. 9. **Connascence of Identity (CoI).** Two elements must reference *the same instance* — two components must share one particular queue object, not an equal one. Strongest and hardest to see. **Remedy:** shrink the scope of shared identity; pass the shared thing explicitly through one owner rather than via globals or ambient lookup. (Some treatments also list **Contranascence** — the requirement that two elements *differ*, e.g. two classes must not share a name in the same namespace, or two migrations must not claim the same version.) ## The three measures — the part most people forget The form alone isn't the verdict. Page-Jones adds three orthogonal dimensions: - **Strength** — how easy is it to detect and safely change? A rename is refactorable by tooling; a shared algorithm across two teams is not. Prefer forms that are *easy to discover and mechanically fixable*. - **Degree** — how many elements participate? One caller depending on argument order is nothing; two hundred call sites, or forty consumers of a wire format, is a migration project. Degree is what turns a benign form into an unmovable one. - **Locality** — how close are the connascent elements? **This is the key insight**: connascence *inside* a small, private scope is cheap regardless of form, because one person can see and change all of it atomically. The same connascence spread across modules, repositories, teams, or deployables is expensive, because the change must be coordinated, versioned, and rolled out in stages. ### The three heuristics 1. **Minimize overall connascence** by decomposing into encapsulated units. 2. **Minimize connascence that crosses encapsulation boundaries**, and maximize connascence *within* them. (This is the precise version of "low coupling, high cohesion" — and note it does *not* say eliminate connascence, which would eliminate the program.) 3. **Where connascence must cross a boundary, convert it to a weaker form** — position → name, meaning → name, algorithm → shared library or explicit versioned schema, timing → explicit signal. A useful corollary: **as locality decreases, connascence should weaken.** Inside a private method, positional tuples and shared algorithms are fine. Across a public API or a network boundary, you want named fields, explicit versioned schemas, and no assumptions about order or timing. ## Why this matters at architecture scale - **It gives a testable definition of a good boundary.** A boundary is well placed when the connascence crossing it is weak (name/type via an explicit, versioned schema) and low-degree, while the strong forms (algorithm, value invariants, identity, execution order) stay inside one unit of ownership. That is a sharper statement of the same idea as Parnas's information hiding, the Common Closure Principle, and bounded contexts. - **It predicts distributed-systems pain.** A shared database schema is high-degree connascence of meaning and position across the worst possible locality. Two services independently implementing the same signing algorithm is CoA at maximum distance — the classic "why did the integration silently break?" A message contract of named, optional, additively-evolved fields is CoN at distance, which is survivable. - **It explains why microservice splits fail.** Splitting a module that contains strong connascence of value or execution order converts an in-process, compiler-visible dependency into a distributed, runtime-only one — same coupling, worse locality, plus network failure modes. The rule "don't split across strong connascence" is a better splitting criterion than "one service per noun". - **It makes reviews concrete.** "This is connascence of algorithm at degree 4 across two repos; let's publish one library and add conformance tests" is actionable in a way "this is tightly coupled" is not. ## Limits Connascence is a lens, not a metric with a number: strength ordering is broadly agreed but not exact, tooling support is thin (some linters catch magic numbers or long positional parameter lists, but nothing measures degree and locality generally), and the vocabulary is far less widely known than SOLID, so you must define it before using it in a review. It also says nothing directly about dependency *direction* or cycles — pair it with DIP and acyclic-dependency thinking. Used well, though, it is the most precise tool available for arguing *where* a boundary belongs and *what specifically* to change when it is in the wrong place.
- Two services independently implement the same signature algorithm. Which connascence is that and how would you fix it?Connascence of algorithm at the worst possible locality — a cross-deployable, runtime-only agreement that no compiler checks, so a change on one side breaks integration silently. Fixes, in order of preference: extract one shared, versioned implementation both call; or, if that's impossible (different languages/teams), publish a formal specification plus cross-implementation conformance test vectors run in both CI pipelines, and version the algorithm explicitly so both sides can negotiate.
- Is strong connascence always bad?No — locality dominates. Strong connascence inside one small, private, single-owner scope is cheap, because one person changes all of it in one atomic commit. The rule is to maximize connascence *within* an encapsulation boundary and minimize (and weaken) what crosses it. Chasing 'no strong connascence anywhere' produces ceremonial indirection with no benefit.
- How would you use connascence to decide whether to split a module into two services?Inventory what crosses the proposed line. If the strong dynamic forms — value invariants, execution order, timing, identity — cross it, don't split: you would convert a compiler-visible in-process dependency into a distributed, runtime-only one, adding network failure modes without reducing coupling. Split where only name/type connascence crosses, expressed as an explicit versioned contract, and where degree is low enough that the contract can evolve additively.
- How does connascence relate to the classic coupling/cohesion taxonomy?It refines it. Control coupling is roughly connascence of meaning (the flag's interpretation) plus position; common coupling on shared globals is typically connascence of value and identity at bad locality; coincidental cohesion is the absence of connascence among elements that were grouped anyway. Connascence adds degree and locality, which the classic ladders lack, and therefore supports arguments about *where* the boundary goes rather than just labeling what exists.
Two people must arrive at a meeting together. Agreeing on a name — "the café on Main" — is weak connascence: if the café is renamed, one message fixes it. Agreeing on "third door from the corner" is positional: the moment a new door is added, both are wrong and neither notices. Agreeing to "both compute the meeting time by the same rule from the moon phase" is connascence of algorithm — silently broken the first time one of them updates the rule. And all of this is trivial if you share an office (high locality) and expensive if you're in different time zones.
saying these in an interview costs you the question
- Reciting the nine forms without the three measures — strength, degree, and locality are what make it actionable.
- Claiming all connascence should be eliminated; zero connascence means the parts don't cooperate at all.
- Ignoring locality and treating strong connascence inside one private function as a defect equal to the same form crossing a service boundary.
- Assuming connascence of type is dangerous because it 'couples types' — it is one of the weakest forms and is compiler-enforced.
- Splitting a module into services across strong dynamic connascence (shared invariants, ordering, timing) and expecting decoupling.
- Treating a shared database schema as an implementation detail rather than high-degree connascence of meaning and position at the worst locality.
- Presenting connascence as a numeric metric with tool support comparable to test coverage.