Blog

Afferent and efferent coupling, explained with numbers from real codebases

What afferent coupling (Ca) and efferent coupling (Ce) actually measure, how to read them together, and what the rest of the metrics on a Striff diagram mean: WMC, DIT, NOC, and encapsulation, illustrated with real values from Spring Framework and Apache Pinot.

Every architecture tool eventually shows you two numbers: afferent coupling and efferent coupling. Most engineers nod, vaguely recall the definitions point in opposite directions, and move on. That’s a shame, because these two numbers, read together, are the closest thing software has to a blood pressure reading for a component.

Here’s what they measure, why they matter, and, using real values from Spring Framework and Apache Pinot, how to tell a healthy number from a warning sign. At the end, we’ll decode the rest of the metric badges you’ll see on a Striff diagram.

The two directions

Both metrics count dependencies on a single component (a class, or a package). The only difference is which way the arrows point.

Afferent coupling (Ca): arrows in. How many components depend on you. This is your blast radius: if you change, this is how many places can break.

Efferent coupling (Ce): arrows out. How many components you depend on. This is your exposure: every outgoing arrow is a reason you might be forced to change.

Afferent vs. efferent: same node, opposite questions

AFFERENT (Ca): who depends on you AvroUtils Ca = 24 Change this → 24 places can break EFFERENT (Ce): what you depend on AbstractBeanFactory Ce = 102 102 reasons this class might be forced to change

Both numbers are real: AvroUtils has an afferent coupling of 24 in Apache Pinot's parsed scope; AbstractBeanFactory reached an efferent coupling of 102 in Spring Framework. Both come from PRs we analyzed with Striff.

The definitions go back to Robert C. Martin’s package-design metrics, which also give us the derived number worth knowing: instability, I = Ce / (Ce + Ca). A component with high Ca and low Ce is stable (hard to justify changing, easy to depend on). One with high Ce and low Ca is unstable (free to change, dangerous to depend on). Neither is bad on its own. Problems start when a component is high on both axes at once.

The four quadrants

Reading Ca and Ce together

High Ca · Low Ce

Stable core

Interfaces, domain types, shared contracts. Everyone depends on them; they depend on little. Healthy, but every change here is expensive by design.

High Ca · High Ce

The danger zone

Many dependents and many dependencies: god classes, "utils" dumping grounds, accidental bridges. Fragile to change, impossible to avoid. AbstractBeanFactory lives here.

Low Ca · Low Ce

Quiet leaf

Self-contained helpers and features. Change freely; almost nothing can break.

Low Ca · High Ce

Orchestrator

Controllers, entry points, wiring code. Volatile but safe: nothing depends on them, so their churn doesn't ripple.

↑ rows: afferent coupling (Ca)columns: efferent coupling (Ce) →

The quadrant a component sits in matters more than either raw number. A Ce of 40 on an orchestrator is Tuesday; a Ce of 40 on a stable core component means every one of its many dependents inherits 40 new reasons to break.

Why the delta beats the absolute number

Here’s where most static-analysis dashboards go wrong: they report absolute values and let you stare at them. But an absolute coupling number without context is nearly meaningless. Spring’s AbstractBeanFactory has had high coupling for fifteen years and Spring works fine. What matters is where a change happens, and which direction it’s trending.

The real Spring finding makes the point:

The same +3 delta, two very different meanings

Leaf helper, Ce 5 → 8
low risk
AbstractBeanFactory, Ce 99 → 102
flagged

Both changes add 3 outgoing dependencies. The first is noise. The second lands on a class hundreds of components depend on, so all of them inherit three new transitive reasons to break. Same diff-size, wildly different blast radius. Risk = delta × afferent coupling, not delta alone.

The Pinot example shows the same logic on the other axis. In apache/pinot #19073, SegmentProcessorAvroUtils grew its efferent coupling from 36 to 44 in one PR, while sitting next to AvroUtils, a contract with 24 dependents, which the same PR modified. Either number alone is unremarkable. Together they describe a class becoming a bridge between the core engine and a plugin, which is exactly what Striff flagged.

The rest of the instrument panel

Coupling is two gauges on the dashboard. On a Striff diagram, every changed class carries a full row of metric badges, each with its delta since the last version. Here’s the row for a class that just absorbed a big refactor, and what each badge means:

The badges on a Striff class box, decoded

NOC: 2±0 DIT: 3±0 WMC: 38+36% ENC: 0.7+38% AC: 5±0 EC: 44+22%
BadgeWhat it measuresWhy you'd care
WMCWeighted method complexity: the summed cyclomatic complexity of the class's methodsThe single best "is this becoming a god class?" gauge. A WMC jumping 36% in one PR means logic is pooling here instead of being distributed.
DITDepth of inheritance tree: how many ancestors the class hasDeep hierarchies make behavior hard to trace; every layer is a place logic can hide. A DIT that grows during a "simple" change deserves a look.
NOCNumber of children: direct subclassesHigh NOC means the class is a contract many things extend. Like high Ca, it multiplies the cost of every change you make here.
ENCEncapsulation ratio: the share of members that are private or protected (0 to 1)A falling ENC means the class is exposing more of its internals, inviting exactly the kind of coupling the other badges then measure.
AC / ECAfferent and efferent coupling, as aboveBlast radius and exposure. The two you now know how to read.

The badges always show the value and the change, because as with coupling, the delta in this PR matters more than the absolute number. A class at WMC 38 is unremarkable; a class that got there this week is a trend.

Read together, the panel tells a story no single metric can. Rising WMC with rising EC and falling ENC is a class absorbing responsibilities, reaching out for more collaborators, and opening its internals to do it: a god class in the making, visible three PRs before anyone would name it that in review.

How to use these numbers on a real team

Skip the thresholds. Rules like “Ce must stay under 20” produce arguments, not architecture. What works:

  • Watch high-Ca components like production config. Any PR that touches a component with dozens of dependents deserves a closer look, especially when the diff looks trivial. Small diffs on high-Ca nodes are where blast-radius accidents live.
  • Treat Ce growth on stable components as a smell. A stable core component that keeps gaining outgoing dependencies is migrating toward the danger-zone quadrant, one convenient import at a time.
  • Track trends, not snapshots. Coupling that grew 99 → 102 this month and 96 → 99 last month is a direction, and directions compound. This matters double when AI tools multiply your PR volume: drift that took a year now takes a quarter.
The uncomfortable part: nobody computes this during review. Ca, Ce, WMC, and the rest aren't in the diff. GitHub doesn't show them. To know that a three-line change grew coupling on a 24-dependent contract, someone has to build the dependency graph of both sides of the PR and compare, per pull request. No human does this by hand, which is why coupling regressions ship silently.

That’s the part Striff automates: it parses every PR into a component graph, computes the full metric panel and its deltas on the spot, and flags only the changes whose position makes them risky: a coupling spike on a high-dependency node, not a helper gaining its fifth import. Install the browser extension and the numbers in this post show up on your own pull requests, automatically.