Leer en español · Lire en français · Auf Deutsch lesen
Section 3.6 of 3.8 · Intermediate · 22 min read
Connascence: a finer measure of coupling
Video lesson
5 min · narrated, with captionsListen only (audio)
Read the transcript
Coupling tells you that two parts are connected. Connascence tells you how, how risky that is, and which refactoring would make it safer. In this lesson you'll learn its forms, and the three ways to judge them.
When two friends agree to meet at the usual place at the usual time, they're bound by an agreement. Nobody else can see it. And if one of them changes the plan, the other must know. Software is full of agreements like that: names, the order of values, what a number secretly means, which step must happen first. Connascence is a way of listing them, judging the risk of each, and making the risky ones weaker, more visible or closer together.
The word comes from Latin for born together, and Meilir Page-Jones introduced it in 1992. Two elements are connascent if a change to one would require a change to the other to keep the system correct. The elements can be anything: a function and its callers, a producer and the consumers of its messages, a database column and every report that reads it.
Static forms can be found by reading the source. The weakest is connascence of name: a caller and a function agree on its name. Renaming is mechanical, and tools find every use. Connascence of type: both sides agree on a type, such as a delivery fee in whole pence. Type checkers and schemas make it visible. Connascence of meaning: both agree on what a value means, such as status four meaning rider assigned. Magic values like that are invisible to tools. Connascence of position: the order of values matters, as with function arguments or the columns in a file. And connascence of algorithm: both sides must compute something the same way, such as the same checksum.
Dynamic forms only show when the system runs, so they're harder to find and usually stronger. Connascence of execution: the order of steps matters. Payment must be authorised before the restaurant is notified. Connascence of timing: when things happen matters. A rider app that gives up after five seconds, while dispatch takes up to eight, will fail. Connascence of value: several values must change together. If the front end and the back end each hold the list of delivery zones, the two lists must always match. And connascence of identity: elements must refer to the very same thing, such as two modules writing within one database transaction.
Every instance can be judged three ways. Strength is how hard the form is to find and change. Name is weak, meaning is stronger, and timing and identity are strongest. Locality is how close the elements are. Positional arguments between two private helpers in one file are harmless. Positional fields in a message between teams are dangerous. Degree is how many elements share it. If status code three appears in twenty eight places owned by three teams, changing its meaning means twenty eight coordinated edits. That gives three moves. Weaken the form, for example by replacing a magic value with a named constant. Move strongly connected elements closer together. Or route a shared convention through one definition, to reduce its degree.
This grid puts it together. The forms run from weakest on the left to strongest on the right, and distance grows from top to bottom. Within one function, every form is acceptable, because whoever changes one side can see the other. Across services, only name and type are healthy: the agreements a published schema makes explicit. Everything stronger is coral: refactor it towards a weaker form, or move the elements closer. And if two services share connascence of value or identity, the boundary is probably in the wrong place. One should own the data, or the two should be one service.
Here's a worked example from a ride-hailing app. Its booking function has grown to eight positional arguments: a rider, four coordinates, a number, true, and zero. Swap the pickup and drop-off coordinates and it still type-checks, then silently sends the driver to the wrong place. That's connascence of position. Two means a large vehicle, true means pay by card, and zero means book now. That's connascence of meaning. And there are thirty one call sites across three components. The refactoring goes in steps. Introduce a location type and keyword arguments. Replace the magic values with named ones. Then group everything into one booking request object. The behaviour hasn't changed. But strong, invisible agreements have become weak agreements of name and type, which the type checker and editor can verify.
The full section has a table of all nine forms, with ways to weaken each, and a closer look at connascence between services. Next, in Section three point seven, we look at Conway's law. See you there.
What you need to know
- Two elements are connascent if a change to one would require a change to the other to keep the system correct; Meilir Page-Jones introduced the idea in 1992.
- Static forms (name, type, meaning, position, algorithm) are visible in the source; dynamic forms (execution, timing, value, identity) only show at run time and are generally stronger.
- Strength is how hard a form is to find and change, locality is how far apart the elements are, and degree is how many elements share the agreement.
- Convert strong forms to weaker ones, keep any strong connascence local, and reduce degree by routing a convention through one definition or owner.
- Replacing positional parameters with named ones and magic values with named enumerations turns strong, invisible agreements into weak ones tools can check.
- Across services only weak forms such as name and type in a published schema are healthy; value or identity connascence between services signals a misplaced boundary.
Why coupling needs a finer vocabulary
Section 3.2 described coupling with levels from Structured Design (content, common, external, control, stamp, data) and with the distributed forms that matter between services. Those levels classify how modules are connected. They say less about two questions engineers face every day: how painful will a change be, and which refactoring would reduce the pain? Connascence answers both.
Connascence (from Latin, "born together") was introduced by Meilir Page-Jones in a 1992 Communications of the ACM article, "Comparing techniques by means of encapsulation and connascence", and developed in his book What Every Programmer Should Know About Object-Oriented Design (Dorset House, 1995), later reworked as Fundamentals of Object-Oriented Design in UML (1999). His definition is simple: two elements are connascent if some change to one would require a change to the other in order to keep the system correct. The elements can be anything: two functions, a function and its callers, a producer and the consumers of its messages, a database column and every report that reads it.
Two features make the idea useful to architects. First, connascence comes in named forms that can be ranked by strength, so you can say not just "these are coupled" but "these are coupled by position, which is worse than coupling by name, and here is how to convert one to the other". Second, it has two further dimensions, locality and degree, that tell you whether a given form is acceptable where it is.
Static forms: visible in the code
Static connascence can be found by reading the source, often by tools, without running the program. From weakest to strongest in the order most commonly given:
Connascence of name: elements must agree on a name. A caller uses calculate_tax; a JSON consumer reads customer_id. It is the weakest form because renaming is mechanical and tools find every use.
Connascence of type: elements must agree on a type. A function expects an integer number of pence; passing a float compiles in Python but corrupts the result. Type checkers and schemas make it visible.
Connascence of meaning (or convention): elements must agree on what a value means. Status 3 means "shipped"; -1 means "not found"; an empty string means "use the default". Such magic values are invisible to tools: nothing links the 3 in the warehouse code to the 3 in the email service.
Connascence of position: elements must agree on the order of values. create_user("Ann", "Smith", "ann@example.com") depends on parameter order; a CSV file depends on column order; a tuple (lat, lon) breaks silently if someone assumes (lon, lat), as GeoJSON does.
Connascence of algorithm: elements must agree on an algorithm. A webhook sender and receiver must compute the same HMAC signature; a client and server must apply the same password-hashing scheme or checksum; every service that routes by customer must use the same hash function to pick a shard.
Dynamic forms: visible only at run time
Dynamic connascence depends on how the system behaves when it runs, so it is harder to find and usually stronger.
Connascence of execution: the order of execution matters. A connection must be opened before data is sent; begin() must precede commit(); an order must be created before its payment is captured.
Connascence of timing: the timing of execution matters. A client's timeout must be longer than the server's normal processing time; a test that sleeps for two seconds hopes an asynchronous job has finished; two threads race to update a balance.
Connascence of value: several values must change together, usually because of an invariant. The list of valid country codes in the web front end and in the back end must match; the total on an order must equal the sum of its lines; the debits and credits of a ledger entry must balance.
Connascence of identity: elements must refer to the same entity, not just an equal one. Two repositories must share the same database transaction for their writes to be atomic; two components must publish to the same queue instance, not two queues with the same configuration.
Page-Jones also named the opposite relationship, contranascence: elements that must differ, such as two classes in one namespace that must not share a name, or two services that must not use the same port. It obeys the same rules.
The nine forms with an example of each
| Form | Kind | Example in a food-delivery app | Typical way to weaken it |
|---|---|---|---|
| Name | Static | The order service calls restaurant.get_menu() | Already the weakest form; keep names meaningful |
| Type | Static | Delivery fee passed as integer pence, not a float of pounds | Type hints, schemas, a Money type |
| Meaning | Static | status == 4 means "rider assigned" | Named constants or an enumeration, shared via one definition |
| Position | Static | quote(2.5, 51.5, -0.12, True) | Keyword arguments, a parameter object, named JSON fields |
| Algorithm | Static | App and server compute the same basket checksum | One shared library or let one side compute and send the result |
| Execution | Dynamic | Payment must be authorised before the restaurant is notified | Encapsulate the sequence in one place (a workflow or saga) |
| Timing | Dynamic | Rider app gives up after 5 s while dispatch takes up to 8 s | Make the interaction asynchronous; derive timeouts from SLOs |
| Value | Dynamic | Front end and back end each hold the list of delivery zones | One owner of the data; others query or subscribe to it |
| Identity | Dynamic | Two modules must write within the same database transaction | Give one module ownership of the unit of work |
Strength, locality and degree
Every instance of connascence can be judged on three properties. Strength is how hard the form is to discover and to change: name is weak because tools find and rename it; meaning is stronger because nothing marks the magic values; timing and identity are strongest because they may only appear under load or in production. The order in the table is the conventional ranking, though authors differ slightly on the static forms (some rank position below meaning).
Locality is how close the connascent elements are. Positional parameters between two private helper functions in the same file are harmless: whoever changes one sees the other. Positional fields in a message between services owned by different teams are dangerous, because the people who must change together do not sit together, do not release together and may not even know about each other.
Degree is how many elements are involved. A magic number shared by two functions is a nuisance; the same number used by forty files in six services is a migration project. Degree can be counted: if the status code 3 appears in 14 places in the warehouse service, 9 in the notifications service and 5 in the reporting database's views, the degree is 28, and changing its meaning means coordinating 28 edits across three teams and at least two deployments, with a data migration for the views.
Strength must fall as distance grows
The guidelines
Page-Jones's advice can be summarised in three statements: minimise connascence overall by breaking a system into encapsulated elements; minimise the connascence that crosses encapsulation boundaries; and maximise the connascence within them. The last one surprises people, but it is simply cohesion restated: things that must change together should live together.
Jim Weirich, the Ruby developer best known as the author of the Rake build tool, revived the idea for a new generation in talks from 2009 onwards, including "The Building Blocks of Modularity" (given at several Ruby conferences in 2009, among them MountainWest RubyConf) (which he jokingly called a grand unified theory of software development) and "Connascence Examined". He condensed the practice into two rules. The rule of locality: the further apart two elements are, the weaker the connascence between them should be. The rule of degree: convert strong forms of connascence into weaker ones, especially where many elements are involved.
In practice this gives three moves, in rough order of preference:
Weaken the form. Replace meaning with name (a named constant), position with name (keyword arguments, named fields), algorithm with name (one shared function both sides call), execution with encapsulation (one method that does the steps in the right order).
Increase locality. If two elements must share a strong form, move them closer: into the same class, module, component or service, owned by the same team.
Reduce degree. If many elements share a convention, route them through one definition, one owner or one adapter so that only a few places know the detail.
Worked example
Worked example: replacing positional parameters and magic values
A ride-hailing app has a function used by the booking API, the scheduler and an admin tool. Its signature has grown over two years: book(rider_id, 51.501, -0.142, 51.515, -0.072, 2, True, 0). Read the connascence and refactor it.
| Position | Eight positional arguments. Swapping pickup and drop-off coordinates still type-checks and silently sends the driver to the wrong place. |
|---|---|
| Meaning | 2 is the vehicle class (1 standard, 2 large, 3 accessible); True means "pay by card"; 0 means "book now" while any other number is a scheduled time in epoch seconds. |
| Degree | A search finds 31 call sites across three components, so every new parameter means 31 edits in a fixed position. |
| Step 1: name over position | Introduce a Location type and keyword-only arguments, so callers write pickup=Location(lat=51.501, lon=-0.142). |
| Step 2: name over meaning | Replace 2 with VehicleClass.LARGE, True with Payment.CARD, and 0 with when=None meaning now. |
| Step 3: reduce degree | Group the arguments into a BookingRequest object; adding an optional field later touches only the code that needs it. |
| Result | Connascence of position and meaning (strong, degree 31) becomes connascence of name and type (weak), which the type checker and IDE can verify and rename automatically. |
Nothing about the behaviour changed; the refactoring only changed which agreements callers must keep and how visible those agreements are.
Do the change in steps that keep old callers working, for example by accepting both forms for one release and logging use of the old one.
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class VehicleClass(Enum):
STANDARD = "standard"
LARGE = "large"
ACCESSIBLE = "accessible"
class Payment(Enum):
CARD = "card"
CASH = "cash"
@dataclass(frozen=True, kw_only=True)
class Location:
lat: float
lon: float
@dataclass(frozen=True, kw_only=True)
class BookingRequest:
rider_id: str
pickup: Location
dropoff: Location
vehicle: VehicleClass = VehicleClass.STANDARD
payment: Payment = Payment.CARD
when: datetime | None = None # None means now
def book(request: BookingRequest) -> str: ...Connascence across service boundaries
Mark Richards and Neal Ford, in Fundamentals of Software Architecture (2020; second edition 2025), present connascence alongside cohesion and coupling as one of the core measures of modularity, and argue that it is as useful between services as within code. Read the forms with a distributed system in mind and each maps onto a familiar problem:
Name and type across services are the agreements a published schema makes explicit: field names and types in an OpenAPI document, an Avro or Protobuf schema, or an event catalogue. These are the forms you want, and contract tests can check them.
Meaning across services is the shared status code or the unexplained flag: what Section 3.2 called semantic coupling. Prefer self-describing values ("status": "rider_assigned") to numbers, and units in field names (fee_pence, timeout_ms) to assumptions. Units deserve particular care: NASA's Mars Climate Orbiter was lost in 1999 because one team's ground software produced impulse figures in pound-force seconds while the navigation software that consumed them expected newton-seconds, connascence of meaning between organisations that nobody had made explicit.
Position across services appears in fixed-width or CSV file exchanges and positional message formats; named fields remove it. Algorithm appears when two services must compute the same price, discount or hash independently; the cure is usually to have one service compute it and send the result.
Execution and timing across services are the hard problems of distributed systems, and timing is the connascence behind the temporal coupling of Section 3.2: a consumer that assumes the producer's database write is visible before the event arrives, a chain of synchronous calls whose timeouts are not nested correctly, a workflow that only works if messages arrive in order. Making interactions asynchronous, idempotent and order-tolerant weakens these forms; patterns such as sagas and the outbox (Chapter 6) exist largely to manage them.
Value and identity across services are the signs of a boundary in the wrong place. If two services must keep a value consistent (the order total in the order service and the invoice total in billing), or must operate on the same row in the same transaction, then either one service should own that data and the other should ask for it, or the two services should probably be one. Strong connascence across a network is the definition of a distributed monolith.
Check your understanding
A function takes (width, height) and a caller passes (height, width). Which form of connascence was broken, and what is the simplest way to weaken it?
Connascence of position. Use keyword arguments or a parameter object so the agreement becomes one of name.
Why is connascence of meaning stronger than connascence of name?
Tools and readers can find every use of a name, but nothing links the separate uses of a magic value such as 3, so changes are easy to miss.
Two services must always update a customer's credit limit in the same transaction. What does that suggest?
Strong connascence of identity or value across a network boundary: the data probably needs a single owner, or the two services belong together.
Why might strong connascence inside a single small function be perfectly acceptable?
Because locality is high: whoever changes one element sees the other immediately, so the cost and risk of keeping them consistent is tiny.
Sources and further reading
Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.
Ask: “Our order and invoicing services both calculate the order total and they sometimes disagree by a penny. How would I describe this coupling in terms of connascence, and what should we change?”