skip to content

Define abstractness (A) for a component, and explain the "main sequence" A + I = 1, the distance metric D = |A + I - 1|, and what the zone of pain and zone of uselessness are.

level: middleimportance: must knowfreq 35%

answer

  1. A = abstract types / total types
  2. Main sequence: A + I = 1
  3. D = |A + I − 1|
  4. (0,0) zone of pain: concrete + depended-on
  5. (1,1) zone of uselessness: abstract + unused

basics

~20 s

A = abstract types / total types in a component (0 = all concrete, 1 = all interfaces). The main sequence is the line A + I = 1: stable components should be abstract, unstable ones concrete. D = |A + I - 1| is how far you sit from that line — smaller is better.

solid answer

~50 s

Abstractness A = (number of abstract classes and interfaces) / (total number of types) in a component, in [0, 1]. Paired with instability I = Ce/(Ca+Ce), it forms a two-dimensional map. The **main sequence** is the line A + I = 1: components that many others depend on (I near 0) should be mostly abstract, so dependents bind to interfaces and the component can be extended without modification; components nobody depends on (I near 1) should be concrete implementations. **Distance from the main sequence** D = |A + I − 1| (or normalized D′ = |A + I − 1| / √2 for perpendicular distance) measures the violation. Two corners are pathological: the **zone of pain** (A≈0, I≈0) — concrete and heavily depended upon, e.g. a concrete database schema or a utility class everyone calls: rigid, change-resistant, hard to extend; and the **zone of uselessness** (A≈1, I≈1) — abstract with no implementers and no dependents: dead abstraction. Treat D as a smell detector, not a target.

code

text · 7 lines
text
Component        A     I     D=|A+I-1|   Reading
----------------------------------------------------------
domain-api      0.95  0.05    0.00      on the main sequence
legacy-utils    0.00  0.10    0.90      ZONE OF PAIN
plugin-spi      1.00  0.95    0.95      ZONE OF USELESSNESS
web-adapter     0.10  0.90    0.00      on the main sequence
order-service   0.40  0.50    0.10      acceptable

go deeper

for a junior

Give the formula for A, state the main sequence line, and name the two bad corners with one example each.

for a middle

Add D, the Stable Abstractions Principle, and the concrete refactorings that move a component toward the line.

for a senior

Discuss volatility as the missing dimension, combine D with VCS churn, and cover the cost of speculative interfaces.

for a principal

Position the metric inside architectural governance: sampled trends, fitness functions for specific rules, explicit language-convention caveats, and resistance to turning D into a target.

### Abstractness ``` A = Na / Nc Na = number of abstract classes + interfaces in the component Nc = total number of types in the component ``` "Abstract" means a type that cannot be instantiated and exists to be implemented/extended — an interface, abstract class, protocol, trait, or (in dynamic languages) a documented duck-typed contract. A = 0: purely concrete. A = 1: nothing but contracts. Why would abstractness matter? Because of the **Open–Closed Principle**: a component that others depend on should be extendable without being modified. Extension without modification requires abstractions to extend. So the more depended-upon a component is, the more of its public surface should be abstract. ### The main sequence Plot every component on a square with I on the x-axis and A on the y-axis. Two corners are trouble: - **(0, 0) — the Zone of Pain.** Maximally stable *and* maximally concrete. Everything depends on it, and there is no abstraction through which to vary it. Changing it is a system-wide edit; extending it is impossible without modification. Real examples: a concrete database schema, a concrete `Utils` class used by every module, a rigid serialized data format, a concrete framework base class. Note the nuance: some inhabitants of this zone are *fine* because they are **non-volatile** — a `String` type, a stable math library, a well-known standard library. Pain only bites when a zone-of-pain component actually changes. - **(1, 1) — the Zone of Uselessness.** Maximally abstract and maximally unstable: abstractions nobody depends on and (often) nobody implements. This is leftover speculative generality — interfaces created "in case we need another implementation" that never arrived. It is dead weight and should be deleted or inlined. The line joining (0, 1) and (1, 0) — that is, **A + I = 1** — is the **main sequence**. Points on it are balanced: fully stable components are fully abstract; fully unstable components are fully concrete; middling components are partly both. ### Distance ``` D = | A + I - 1 | (range 0..1, 0 = on the line) D' = | A + I - 1 | / sqrt(2) (true perpendicular distance, some tools use this) ``` A component with A = 0.0 and I = 0.1 has D = 0.9 — deep in the zone of pain. A component with A = 0.9, I = 0.9 has D = 0.8 — deep in uselessness. A component at A = 0.4, I = 0.7 has D = 0.1 — fine. Aggregate uses: mean D over the codebase as a rough architectural-health trend line; per-component D sorted descending as a refactoring backlog. ### Related principles - **Stable Abstractions Principle (SAP)**: *a component should be as abstract as it is stable.* This is literally the main sequence restated. Together with the Stable Dependencies Principle (depend toward lower I), SAP is the component-level restatement of the Dependency Inversion Principle: high-level policy is stable and abstract; low-level detail is unstable and concrete. ### How to move a component off the line — the actual refactorings - **Stuck at low A, low I (pain):** extract interfaces for the depended-upon behavior, move them into an abstraction component, and have dependents import the abstractions. Or accept it if the component is genuinely frozen. - **Stuck at high A, high I (uselessness):** delete the unused abstractions, or collapse the interface into its single implementation. "One interface, one implementation, forever" is the classic tell. - **Concrete component that everything depends on because it holds shared data structures:** consider whether the types belong to a shared kernel (accepted, low volatility) or whether each consumer should own its own model. ### Caveats you should voice in an interview 1. **Language dependence.** A is easy to compute in Java/C#/Kotlin; in Python, Go, JavaScript, or Rust the notion of "abstract type" maps imperfectly (Go interfaces are structural and usually declared by the *consumer*; Python has ABCs and Protocols; TypeScript interfaces vanish at runtime). Report the counting convention. 2. **Interfaces are not free.** Chasing A upward produces one-implementation interfaces, indirection, and harder navigation. D is a smell detector; near-zero D is not a goal. 3. **It ignores volatility.** The metric cannot tell an inert concrete component from a churning one. Combine D with change frequency from version-control history — high D **and** high churn is where to act. 4. **Goodhart's law.** The moment D becomes a KPI, teams add empty interfaces to game it.

  • Can a component legitimately sit in the zone of pain?
    Yes, if it is non-volatile. Standard library types, a frozen shared value-type library, or a stable math kernel are concrete and heavily depended on but never change, so the rigidity costs nothing. Pair D with change-frequency data before acting.
  • How do abstractness and instability relate to the Dependency Inversion Principle?
    They are its component-scale form. DIP says high-level policy should not depend on low-level detail but on abstractions; on the A/I map that means the stable end of every dependency chain is the abstract end, which is exactly the Stable Abstractions Principle and the main sequence.
  • What is wrong with making D a team KPI?
    It is trivially gamed by adding interfaces with a single implementation, which increases indirection without decoupling anything. Use D to generate a candidate list for human review, and guard specific architectural rules with fitness functions instead.

Think of a power grid. High-voltage trunk lines (stable, everyone depends on them) are standardized to a published spec — an abstraction — so any generator or substation can plug in. The lamp on your desk (nothing depends on it) is fully concrete and can be replaced tonight. A concrete, one-off, non-standard trunk line that every substation is welded to is the zone of pain; a published connector spec that no device and no supplier ever uses is the zone of uselessness.

saying these in an interview costs you the question

  • Claiming higher abstractness is always better — that manufactures the zone of uselessness
  • Saying the zone of pain is always a defect, ignoring non-volatile components like standard libraries
  • Mixing up the corners: pain is concrete+stable, uselessness is abstract+unstable
  • Computing A from 'number of interfaces' without dividing by total types
  • Treating D = 0 as a target to optimize instead of a smell to investigate

context