In component-level design metrics, what do Instability (I) and Abstractness (A) measure, and how is each computed?
answer
- Ce = exits (I depend on), Ca = arrives (depends on me)
- I = Ce / (Ce + Ca)
- A = abstract types / total types
- I=0 stable & painful to change; I=1 free to change
- SDP rides I, SAP rides A
basics
~20 sInstability I = outgoing dependencies / (outgoing + incoming). It says how easily a component can be forced to change. Abstractness A = abstract types / all types. It says how much of the component is interfaces rather than concrete code. Both run 0 to 1.
solid answer
~50 sBoth metrics describe a component (a deployable/releasable grouping such as a package, module, or JAR), not a single class. Efferent coupling Ce counts types inside the component that depend on types outside it; afferent coupling Ca counts types outside that depend on types inside. Instability I = Ce / (Ce + Ca), from 0 (maximally stable: many dependents, no dependencies — it cannot be forced to change, and changing it hurts) to 1 (maximally unstable: depends on everything, nothing depends on it — free to change). Abstractness A = Na / Nc, abstract classes plus interfaces over total types, from 0 (all concrete) to 1 (all abstract). I is the axis of the Stable Dependencies Principle (depend toward stability); A is the axis of the Stable Abstractions Principle (a stable component should be abstract). Plotted together they give the A/I graph.
code
pseudocode · 10 lines// per component (package / module / jar)
Ce = count(types inside that reference types outside) // outgoing
Ca = count(types outside that reference types inside) // incoming
I = Ce / (Ce + Ca) // 0 = maximally stable, 1 = maximally unstable
A = Na / Nc // abstract+interface types / total types
// example: an "orders-api" module of 8 interfaces, 0 classes,
// referenced by 12 outside types, referencing 0 outside types
// A = 8/8 = 1.0 , I = 0/(0+12) = 0.0go deeper
State both formulas and the 0..1 meaning of each endpoint. Getting the Ce/Ca direction right (efferent = outgoing) already puts you ahead.
Add that stability means cost-of-change, not defect rate, and connect I to the Stable Dependencies Principle and A to the Stable Abstractions Principle.
Discuss that both numbers are functions of where you drew component boundaries, and how you would gather them (JDepend/NDepend/ArchUnit) on a real modular codebase.
Frame them as proxies: A is a crude type count that says nothing about whether abstractions are actually used for inversion, and the metrics are language-sensitive. Use them as trend signals on chosen module boundaries, not targets.
### What is being measured These metrics come from Robert C. Martin's component-design work (*Agile Software Development*, later *Clean Architecture*). They are computed **per component**, where a component is the smallest unit you can independently release or deploy — a package, module, assembly, JAR, npm package, Go package, etc. They are *not* class-level metrics. ### Coupling counts - **Efferent coupling (Ce)** — "e" for *exiting*. The number of types **inside** the component that depend on types **outside** it. These are the component's own dependencies: what it needs to compile/run. - **Afferent coupling (Ca)** — "a" for *arriving*. The number of types **outside** the component that depend on types **inside** it. These are its dependents: who would break if it changed. A dependency here means any compile-time/source reference: inheritance, implementation, field/parameter/return types, constructor calls, static calls. ### Instability ``` I = Ce / (Ce + Ca) range 0..1 ``` - **I = 0 — maximally stable.** Ce = 0: the component depends on nothing outside; Ca > 0: many things depend on it. It is *hard to change* — not because the code is good, but because changing it forces changes on every dependent. Stability here means **"resistance to change"**, an effort/consequence measure, not "bug-free" or "rarely modified". - **I = 1 — maximally unstable ("irresponsible and independent").** Ca = 0: nothing depends on it, so nobody is hurt when it changes; Ce > 0: it depends on other components, so it can be *forced* to change by them. Application entry points, UI shells, main/composition roots naturally sit here — and that is correct. - If Ce = Ca = 0 the component is isolated and I is undefined (tools usually report 0 or skip it). Mnemonic: **stability = number of reasons you *cannot* change something**. Something everybody depends on is stable; something nobody depends on is free to move. ### Abstractness ``` A = Na / Nc range 0..1 ``` - **Nc** = total number of types (classes/structs/interfaces) in the component. - **Na** = number of *abstract* types: interfaces, abstract classes, pure protocols/traits — types that cannot be instantiated directly and exist to be implemented. - **A = 0** — entirely concrete implementation code. **A = 1** — nothing but interfaces/abstract types; the component defines a contract and contains no implementation. A is deliberately a crude count. It does not weight types by size or by how often they are used; it just asks "how much of this component is contract versus flesh?" ### Why the pair matters Each axis encodes one principle: - **SDP — Stable Dependencies Principle:** a component should depend only on components at least as stable as itself, i.e. dependencies should point toward **lower** I. A violation is a stable component (low I) reaching out to a volatile one (high I) — the volatile thing can then break everything. - **SAP — Stable Abstractions Principle:** a component that is stable (low I) should be **abstract** (high A), so that it can be extended without being modified — this is the component-scale expression of the Open/Closed and Dependency Inversion principles. Conversely, an unstable component should be concrete, because concrete code is easy to change and nothing depends on it. Plotting A on one axis against I on the other produces the **A/I graph**, on which the ideal line A + I = 1 (the *main sequence*) and the pathological corners (0,0) and (1,1) are defined. ### Practical notes and edge cases - **Language sensitivity.** In languages without an `interface` keyword (Go's implicit interfaces, duck-typed Python/JS/Ruby), Na is hard to compute; tools approximate with ABCs, protocols, or type-only declarations. The metric is most meaningful in nominally-typed languages (Java, C#, Kotlin, TypeScript, C++). - **Boundary sensitivity.** Both numbers change completely if you redraw component boundaries. Merging two packages converts inter-component edges into internal edges, dropping both Ce and Ca. So the metrics measure your *packaging decisions* as much as your code. - **Internal edges don't count.** Dependencies among types inside the same component are invisible to Ce/Ca; cohesion metrics (REP/CCP/CRP) cover that side. - **Tooling.** JDepend, NDepend, Structure101, Sonar plugins, ArchUnit-style custom rules, and `jdeps`-based scripts compute these. In modular monoliths (e.g. Spring Modulith) they map naturally to modules.
- Why is a component with many incoming dependencies called "stable" when its code may change often in practice?Because "stable" here means resistance to change, not observed change frequency. Many dependents make change expensive and risky, so the component is under pressure to stay put. If such a component *does* churn a lot, that is exactly the smell the metrics are meant to expose.
- Can Instability be computed for a single class instead of a component?You can compute the ratio mechanically, but it loses its meaning. The metric is about release/deploy units — what forces a *rebuild and redeploy* of others. Class-level coupling questions are better answered with fan-in/fan-out, LCOM, or coupling-between-objects metrics.
- What does I = 1 with Ce = 3, Ca = 0 tell you about where that component sits in the system?Nothing depends on it, so it is a leaf — typically an application entry point, UI shell, test fixture module, or composition root. That is a healthy place to be as long as it is concrete (low A).
Think of a load-bearing wall versus a poster on it. The wall is 'stable' — nothing about it depends on the poster, but the whole house depends on the wall, so moving it is expensive. The poster is 'unstable' — it hangs off the wall and nobody hangs off the poster, so you can swap it any afternoon. Instability measures how many things fall down if you touch this piece.
saying these in an interview costs you the question
- Saying "unstable means the code is buggy or changes frequently" — it means nothing depends on it, so change is cheap
- Reversing afferent and efferent (efferent = outgoing/exiting, afferent = arriving/incoming)
- Claiming low Instability is always good and high Instability is always bad — UI shells and main() modules should be unstable
- Computing A by counting methods or lines instead of abstract types over total types
- Applying the metrics per class rather than per releasable component