skip to content

What does the Stable Abstractions Principle (SAP) state about the relationship between how stable a software component is and how abstract it should be?

level: juniorimportance: must knowfreq 55%

answer

  1. Stable = hard to change, not rarely changed
  2. I = Ce / (Ca + Ce); A = Na / Nc
  3. Stable ⇒ abstract; unstable ⇒ concrete
  4. Extend without modifying = OCP for components
  5. Zone of Pain vs Zone of Uselessness

basics

~20 s

SAP says: the harder a component is to change because many others depend on it, the more abstract it should be. Stable components should expose interfaces/abstract types; volatile, concrete code belongs in components few things depend on.

solid answer

~50 s

SAP: "a component should be as abstract as it is stable." Stability here is not "rarely changes" — it means "hard to change," measured by incoming dependencies: a component many others depend on cannot move without breaking them. Such a component is a bad place for volatile implementation detail, because every business change forces a widely felt edit. The escape hatch is abstraction: interfaces and abstract types can be *extended* by adding new implementations elsewhere without modifying the stable component, so it stays rigid in position yet flexible in behavior. The symmetric half: unstable components (few or no dependents, many outgoing dependencies) are cheap to change, so they should hold the concrete, volatile implementations. Violating either half gives you the "Zone of Pain" (stable and concrete: rigid, painful to change) or the "Zone of Uselessness" (abstract with nobody depending on it).

go deeper

for a junior

State the slogan correctly and unpack "stable" as "many things depend on it, so it's hard to change," then give the two halves: stable ⇒ abstract, unstable ⇒ concrete.

for a middle

Add the metrics (I = Ce/(Ca+Ce), A = Na/Nc) and the reasoning: abstraction lets a stable component be extended without modification, so dependents don't break.

for a senior

Bring in the Main Sequence, the two bad zones, and the link to DIP/OCP at component scale; note that stable-and-concrete is only painful when the component is volatile.

for a principal

Frame SAP as volatility risk management across the deployment graph: decide which components are load-bearing, invest abstraction there deliberately, and treat A/I/D as a conversation starter rather than a gate — acknowledging the metrics ignore volatility, dependency weight, and runtime coupling.

### The vocabulary first **Component** — the unit of deployment/release: a jar, assembly, package, module, npm package, shared library. SAP is a *component-level* principle, not a class-level one. **Stability (the counter-intuitive part).** In component design, "stable" does **not** mean "changes rarely." It means **hard to change** — a lot of other code is standing on it, so moving it causes a lot of breakage. Robert C. Martin measures this with coupling counts: - **Ca (afferent couplings)** — number of classes *outside* the component that depend on classes *inside* it (incoming arrows, "my dependents"). - **Ce (efferent couplings)** — number of classes *inside* the component that depend on classes *outside* it (outgoing arrows, "my dependencies"). - **Instability `I = Ce / (Ca + Ce)`**, ranging 0..1. `I = 0` = maximally **stable** (many dependents, depends on nothing — nothing can push it around, and it can't move without hurting others). `I = 1` = maximally **unstable** (depends on many things, nobody depends on it — free to change at will). **Abstractness.** A component is abstract to the degree that its types are abstract — interfaces, abstract classes, protocols, pure signatures with no implementation. Metric: **`A = Na / Nc`**, abstract types divided by total types, also 0..1. ### The principle > **A component should be as abstract as it is stable.** Formally: abstractness should increase with stability; `A` should rise as `I` falls. ### Why it must be true A stable component is where dependencies converge. Two forces collide there: 1. If it holds **concrete, volatile logic**, every business rule change edits it — and every edit ripples out to every dependent, forcing recompilation, retesting, and redeployment. This is **rigidity**: the code you most need to change is the code you least can. 2. But you cannot just "make important components change less" by decree — requirements move. Abstraction resolves the collision. If the stable component holds only *interfaces* (a `PaymentGateway` interface, a `Repository` port), it can be **extended without being modified** — you add a new implementing class in some other, unstable component. The Open/Closed Principle applied at component scale. The stable thing stays put; behaviour still evolves. The mirror half is equally load-bearing: **unstable components should be concrete.** They have few dependents, so changing them is cheap — that is exactly where volatile implementation belongs. An abstract component that nobody depends on is dead weight: the abstraction cost was paid, the benefit (shielding dependents) was never collected. ### The dependency direction that falls out SAP implies that source-code dependencies point **toward abstraction**, i.e. toward the stable components — which is the Dependency Inversion Principle expressed at component granularity. High-level policy lives in stable abstract components; low-level detail (SQL, HTTP clients, frameworks, UI) lives in unstable concrete components that depend inward and are plugged in at runtime. ### The two failure zones Plot components on `A` (vertical) vs `I` (horizontal): - **Zone of Pain** — `A≈0, I≈0`: stable *and* concrete. Rigid, cannot be extended, and everyone breaks when it changes. Classic inhabitants: a shared database schema, a "common utils" jar full of concrete helpers, a shared DTO/model library. - **Zone of Uselessness** — `A≈1, I≈1`: maximally abstract with no dependents. Leftover interfaces nobody implements or calls. - The desirable band is the diagonal between the two extremes — the **Main Sequence**, `A + I = 1`. ### Important nuance Zone-of-Pain membership is only painful for **volatile** components. Non-volatile stable concrete code — a string class, a math library, a frozen protocol codec — sits there harmlessly because it genuinely never changes. SAP is about *volatility risk*, not about mechanically maximizing interface count. ### Common misapplication SAP is not "wrap everything in an interface." Abstraction has real cost — indirection, harder navigation, more files, speculative generality. Apply it where dependency convergence plus expected change actually coincide.

  • If "stable" doesn't mean "unchanging," how can a component be both stable and frequently changing?
    It can — that's exactly the pathological case SAP warns about. Stability measures how hard change is (how many dependents you break), not how often change is requested. A widely depended-on, concrete, volatile component is high-stability *and* high-churn: every change is expensive and far-reaching. The fix is to make the stable part abstract and push the churn into unstable implementations behind it.
  • Does SAP mean every stable component must be 100% interfaces?
    No — it's a gradient, not a binary. Abstractness should be *proportional* to stability. A component with I = 0.5 is fine around A ≈ 0.5. Also, stable concrete components that are genuinely non-volatile (a math or string library, a frozen wire-format codec) are acceptable, because the whole risk SAP prices in is the cost of *change*.

Think of a building's foundation versus its furniture. The foundation is 'stable' — everything rests on it, so you cannot move it without wrecking the house; therefore it must be designed as a generic load-bearing surface (an abstraction) that many different floor plans can sit on. The furniture is 'unstable' — nothing rests on it, so it can be specific, opinionated, and swapped freely. Pouring one specific floor plan into the foundation is the Zone of Pain; building an elaborate universal foundation for a shed nobody puts on it is the Zone of Uselessness.

saying these in an interview costs you the question

  • Saying "stable means it doesn't change often" — it means it's hard to change because many things depend on it
  • Believing stability is a property you choose rather than a consequence of the incoming dependency count
  • Concluding SAP means wrapping every class in an interface
  • Thinking abstract components are always good — an abstract component with no dependents is in the Zone of Uselessness
  • Confusing SAP with SRP or with the class-level Dependency Inversion Principle

context