skip to content

Explain the Stable Dependencies Principle and the Stable Abstractions Principle, including the instability (I) and abstractness (A) metrics, the Main Sequence, and the Zone of Pain and Zone of Uselessness.

level: seniorimportance: should knowfreq 34%

answer

  1. I = Ce/(Ca+Ce): 0 stable, 1 unstable
  2. A = abstract types / total types
  3. instability must decrease along arrows
  4. A + I = 1 Main Sequence, D = |A+I−1|
  5. (0,0) Pain, (1,1) Uselessness

basics

~20 s

SDP: depend in the direction of stability — a component should only depend on components harder to change than itself. SAP: the more stable a component, the more abstract it should be. Instability I = outgoing/(incoming+outgoing) dependencies; abstractness A = abstract types / all types. Good components sit near the line A + I = 1.

solid answer

~1 min

**SDP** — "depend in the direction of stability": each component should only depend on components at least as stable as itself. Stability here means *difficulty of change*, driven mostly by how many things depend on you: many incoming dependencies make a component responsible (hard to change), while many outgoing ones make it dependent (easy to change but easily broken). Quantified as **instability I = Ce / (Ca + Ce)**, where Ca = incoming (afferent) couplings and Ce = outgoing (efferent) couplings; I = 0 is maximally stable, I = 1 maximally unstable. SDP says I must decrease along the direction of dependency. **SAP** — "a component should be as abstract as it is stable": since stable components are hard to change, they must be *extensible* instead, i.e. made of interfaces/abstract types. **Abstractness A = abstract types / total types**, 0 to 1. Plotting A against I, healthy components lie near **A + I = 1**, the *Main Sequence*; distance D = |A + I − 1| measures how far off. The two bad corners: (0,0) is the **Zone of Pain** — concrete and heavily depended upon, rigid and painful to change; (1,1) is the **Zone of Uselessness** — abstract with nobody depending on it, i.e. dead abstraction. Treat these as rough smells, not targets; some Zone-of-Pain components (a stable schema, a string library) are perfectly fine because they are non-volatile.

code

text · 6 lines
text
Component        Ca   Ce    I = Ce/(Ca+Ce)   A     A+I   D=|A+I-1|
---------------------------------------------------------------
domain-api       12    0    0.00             0.90  0.90   0.10  ok (stable + abstract)
web-adapter       0    7    1.00             0.00  1.00   0.00  ok (unstable + concrete)
legacy-utils     15    2    0.12             0.05  0.17   0.83  Zone of Pain -> add seams / split
future-ports      0    1    1.00             1.00  2.00   1.00  Zone of Uselessness -> delete

go deeper

for a junior

Know the slogans: depend toward things that are harder to change, and the things everyone depends on should mostly be interfaces.

for a middle

State both formulas and what I = 0 versus I = 1 means, and identify a violation as a dependency whose instability increases along the arrow.

for a senior

Add the Main Sequence, D, and the two zones, and explain the DIP-based repair for SDP violations with a concrete example.

for a principal

Emphasize that these are diagnostics, not targets: correlate with volatility and churn, note that I ignores volatility and A ignores abstraction quality, and describe how you would use the report to prioritize refactoring.

## Vocabulary (define before using) - **Component**: an independently releasable unit (package/library/module). - **Afferent coupling, Ca**: number of components outside this one that depend on it — *incoming* arrows. - **Efferent coupling, Ce**: number of outside components this one depends on — *outgoing* arrows. - **Stable** here does **not** mean "bug-free" or "rarely edited". It means **hard to change** — many other components would have to move if it changed. A component with 20 dependents is stable because change is expensive, whether or not you want it to be. - **Abstract type**: an interface or abstract class — a name with a contract but no full implementation. ## Stable Dependencies Principle (SDP) > **Depend in the direction of stability.** A component should depend only on components that are more stable than itself. The rationale: you cannot make a component "changeable" if something rigid sits underneath it. Volatile components must be at the *top* of the dependency graph (many outgoing, few incoming), and stable ones at the *bottom*. **Metric — instability:** ``` I = Ce / (Ca + Ce) 0 ≤ I ≤ 1 I = 0 → no outgoing, all incoming → maximally stable (rigid, responsible) I = 1 → all outgoing, no incoming → maximally unstable (easy to change, irresponsible) ``` **SDP restated:** for every dependency edge A → B, `I(A) ≥ I(B)`. Instability must *decrease* as you follow arrows. **Violation and the fix.** If a stable component (I ≈ 0.1) is forced to depend on a volatile one (I ≈ 0.9), you have inverted the safety property. Fix with DIP: extract the needed behavior into an interface component with I ≈ 0 that both point to. That is precisely why DIP-created interface packages exist — they are *stable* components created deliberately to be depended upon. ## Stable Abstractions Principle (SAP) > **A component should be as abstract as it is stable.** A maximally stable component is depended upon by everything, so editing it is expensive. If it must nevertheless accommodate change, the only non-breaking way is *extension* — new implementations of its abstractions (this is the Open/Closed Principle at component scale). Hence stable components should consist largely of interfaces and abstract types; unstable components (leaf plugins, adapters, apps) should be concrete. **Metric — abstractness:** ``` A = (number of abstract types in the component) / (total types) 0 ≤ A ≤ 1 ``` ## The A/I graph and the Main Sequence Plot A (vertical, 0→1) against I (horizontal, 0→1). Two corners are pathological: - **(I=0, A=0) — Zone of Pain.** Very concrete, very heavily depended upon. Rigid: any change breaks many dependents, and you cannot extend it because there is nothing abstract to implement. Classic residents: a giant concrete utility library, a shared concrete data schema, a god-object domain package. - **(I=1, A=1) — Zone of Uselessness.** Highly abstract but nothing depends on it: interfaces nobody implements or calls. Dead weight and confusion. Avoiding both corners means clustering along the line from (0,1) to (1,0): ``` A + I = 1 ← the Main Sequence D = |A + I − 1| ← distance from it (0 = on the line, 1 = worst corner) ``` Useful practical readings: high D with low I and low A → break the concrete stable component apart and put interfaces in front of it. High D with high I and high A → likely a speculative abstraction; delete it or find its user. ## Crucial caveats (these separate a senior answer from metric-worship) 1. **These are heuristics, not scores to optimize.** Chasing D → 0 across a codebase produces interfaces for their own sake. Use the graph to *find outliers worth a conversation*. 2. **Zone of Pain is fine for genuinely non-volatile things.** A string library, a math library, or a stable database schema sits at (0,0) and causes no pain because it does not change. The zone is about *volatility × dependents*, and volatility is not captured by I at all. 3. **A is a shallow proxy.** Counting abstract types says nothing about whether the abstractions are good, owned by the right side, or leak details (DIP clause 2). 4. **Ca/Ce are graph-shape metrics.** They ignore how *deeply* a dependent uses a component; one edge might be a single constant and another the entire API surface. 5. **Component boundaries drive the numbers.** Re-partitioning packages changes every metric, so cross-project comparisons are meaningless — track trends within one codebase. ## How to use it in real work Run a component-metrics report, sort by D, and inspect the top offenders. Ask for each: is it volatile? who depends on it? would an interface seam actually be used? Pair the numbers with change history (git churn) — a Zone-of-Pain component with high churn is the genuine emergency; the same component with no churn in two years is fine.

  • A component measures I = 0.1 and A = 0.05 — deep in the Zone of Pain. Is that automatically a problem?
    No. The zone hurts only when the component is volatile. A stable, rarely changing library or schema can sit there safely. Cross-check the metric against change history; high churn plus many dependents is the real emergency.
  • How does DIP relate to the Stable Dependencies Principle?
    DIP is the repair tool for SDP violations: when a stable component would have to depend on a volatile one, you extract an abstraction with I near 0 that both depend on, restoring decreasing instability along the arrows.
  • Why should the most stable components be the most abstract?
    Because they are expensive to modify, the only cheap way for them to accommodate change is extension — new implementations of their interfaces — which is the Open/Closed Principle applied at component scale.

Building floors: put the heavy, hard-to-move structure at the bottom and the light, frequently rearranged furniture on top. And because the foundation cannot be moved, it exposes standard anchor points (abstractions) so the floors above can be reconfigured without touching it.

saying these in an interview costs you the question

  • Interpreting 'stable' as bug-free or well-tested instead of hard to change.
  • Inverting the formula — claiming many dependents makes a component unstable.
  • Treating D = 0 as a target to optimize codebase-wide, producing interfaces for their own sake.
  • Assuming every Zone of Pain component must be refactored, ignoring whether it is actually volatile.
  • Believing a high abstractness score means the abstractions are good ones.
  • Comparing A/I metrics across projects with different component granularity.

context