skip to content

What is the "main sequence" on the Abstractness/Instability graph, and how is a component's distance D from it computed and interpreted?

level: middleimportance: must knowfreq 38%

answer

  1. Main sequence = the line A + I = 1
  2. Good corners: (0,1) abstract+stable, (1,0) concrete+unstable
  3. D = |A + I − 1| (÷√2 in the original)
  4. Below the line → (0,0) pain; above → (1,1) uselessness
  5. Rank and trend D, don't gate on an absolute

basics

~20 s

Plot Abstractness A against Instability I. The main sequence is the line A + I = 1, running from abstract-and-stable (0,1) to concrete-and-unstable (1,0). Distance D = |A + I − 1|; 0 is ideal, 1 is worst.

solid answer

~60 s

The A/I graph plots Instability I = Ce/(Ce+Ca) on the x-axis and Abstractness A = Na/Nc on the y-axis; every component is a point in the unit square. The main sequence is the diagonal A + I = 1 connecting (I=0, A=1) — maximally stable and maximally abstract, a pure contract everyone depends on — with (I=1, A=0) — maximally unstable and fully concrete, a leaf nobody depends on. It expresses the Stable Abstractions Principle: a component should be as abstract as it is stable. Points on the line are balanced; points off it are unbalanced in one of two ways. The metric is D = |A + I − 1| (normalized, range 0..1); Martin's original form divides by √2 to get true perpendicular distance, range 0..0.707. Interpretation: D near 0 = healthy; D approaching 1 means the component sits in a pathological corner — (0,0) the zone of pain, or (1,1) the zone of uselessness. D is best used as a ranking and trend signal across components, not as an absolute pass/fail target.

code

pseudocode · 9 lines
pseudocode
I = Ce / (Ce + Ca)          // 0 stable  .. 1 unstable
A = Na / Nc                 // 0 concrete .. 1 abstract

signed = A + I - 1          // -1 .. +1  (direction!)
D      = abs(signed)        // 0 .. 1    (normalized distance)
// D_original = abs(signed) / sqrt(2)   // 0 .. 0.707

if (signed < -0.7) flag("drifting toward zone of pain (stable + concrete)")
if (signed > +0.7) flag("drifting toward zone of uselessness (unstable + abstract)")

go deeper

for a junior

Draw the square, name the axes, state the line A + I = 1 and the formula D = |A + I − 1|, and say 0 is good.

for a middle

Add the two healthy endpoints and what each direction off the line means (zone of pain below, zone of uselessness above), plus a worked number.

for a senior

Explain that D discards direction, that volatility decides whether a bad D matters, and how you'd remediate — extract interfaces into their own component, invert dependencies, or redraw boundaries.

for a principal

Position D as an architectural fitness function: track distribution and trend in CI, exclude generated/test/DTO modules, and refuse to make it a hard target because A is a crude type count that rewards interfaces nobody inverts against.

### Setting up the graph For each component (a package/module/JAR — a unit you can release independently) compute two ratios: - **I (Instability) = Ce / (Ce + Ca)** where Ce = outgoing dependencies (types inside depending on types outside) and Ca = incoming dependencies (types outside depending on types inside). I = 0 means nothing can force it to change but many depend on it; I = 1 means nothing depends on it and it depends on others. - **A (Abstractness) = Na / Nc** where Na = interfaces + abstract types, Nc = total types. A = 0 fully concrete, A = 1 pure contract. Plot I horizontally, A vertically. Every component is a dot inside the unit square [0,1] × [0,1]. ### The two good corners and the line between them Only two corners of the square are healthy: - **(I=0, A=1)** — maximally stable *and* maximally abstract. Everyone depends on it, and it is pure interface, so it can be extended by adding new implementations without modifying it. Think of a `payments-api` module of interfaces and value types. - **(I=1, A=0)** — maximally unstable *and* fully concrete. Nobody depends on it, so its concreteness costs nothing and it is free to change. Think of the application's `main`/composition root or a UI shell. The straight line joining those two corners is `A + I = 1` — the **main sequence** (the name is borrowed from the astronomers' Hertzsprung–Russell diagram, where normal stars fall along a diagonal band). It states the ideal trade-off: **a component's abstractness should be proportional to its stability**. Not every component must be at an endpoint; a mid-line point like (I=0.5, A=0.5) — partly abstract, moderately depended-upon — is perfectly balanced. ### The distance metric D ``` D = |A + I − 1| / √2 (original: perpendicular distance, range 0 .. 0.707) D' = |A + I − 1| (normalized, range 0 .. 1) ← what most tools report ``` Modern tooling almost always reports the normalized form and simply calls it D. Because the sign is dropped by the absolute value, D alone does **not** say *which* way a component is off-balance — you must look at A + I − 1: - **A + I − 1 < 0** → the point is *below* the line, drifting toward **(0,0)**: stable and concrete — the **zone of pain**. Many depend on it, and it is all implementation, so changes are expensive and rippling. - **A + I − 1 > 0** → the point is *above* the line, drifting toward **(1,1)**: unstable and abstract — the **zone of uselessness**. Abstractions nobody uses; dead weight. Some authors add a signed variant, **distance D̂ = A + I − 1** (range −1..+1), precisely so the direction is preserved. ### Worked examples | Component | Ce | Ca | I | Na/Nc | A | A+I−1 | D | Reading | |---|---|---|---|---|---|---|---|---| | `order-api` (interfaces) | 0 | 24 | 0.00 | 9/9 | 1.00 | 0.00 | 0.00 | on the line, ideal contract | | `web-ui` | 11 | 0 | 1.00 | 0/30 | 0.00 | 0.00 | 0.00 | on the line, ideal leaf | | `core-util` | 1 | 40 | 0.02 | 0/26 | 0.00 | −0.98 | 0.98 | deep in the zone of pain | | `legacy-abstractions` | 6 | 0 | 1.00 | 7/7 | 1.00 | +1.00 | 1.00 | zone of uselessness | | `billing` | 5 | 5 | 0.50 | 3/12 | 0.25 | −0.25 | 0.25 | mildly concrete-heavy, acceptable | ### How to actually use D 1. **Rank, don't gate on an absolute.** Sort components by D descending and look at the top few. A blanket rule like "D < 0.2 for every module" produces interface-for-interface's-sake churn. 2. **Watch the trend.** D rising release over release for a given component is far more informative than its instantaneous value. Some teams chart mean D and its variance as an architectural fitness function in CI. 3. **Check direction and volatility together.** High D toward (0,0) only *hurts* if the component actually changes. A concrete, universally-depended-on, and utterly frozen component (a string library, a stable wire-format DTO set) has a terrible D and no problem — this region is sometimes called the **zone of exclusion**'s benign neighbour, and Martin explicitly notes that non-volatile concrete components are fine there. 4. **Remember boundaries dominate.** Splitting or merging modules changes Ce, Ca, and Nc at once. If a module is off the line, one legitimate fix is *repackaging* — e.g. extracting its interfaces into their own component (pushing that new component to A≈1, I≈0) and leaving the implementations in a concrete, unstable one. ### Caveats - D says nothing about **cohesion**, **cycles**, or whether abstractions are used for real dependency inversion — an interface implemented by exactly one class in the same module raises A without buying anything. - Components with very few types produce wildly noisy A (a 2-type module jumps 0 → 0.5 → 1). - Generated code, test modules, and DTO/protobuf packages skew results and are usually excluded.

  • Two components both report D = 0.8. Why might one be urgent and the other harmless?
    D drops the sign. One may be at (I≈0, A≈0) — stable and concrete, the zone of pain — and the other at (I≈1, A≈1) — unstable and abstract, the zone of uselessness. They need opposite fixes. And even in the zone of pain, a component that never changes (a frozen utility or wire format) costs nothing, whereas one that churns weekly is a serious rigidity problem.
  • Why is the original formula divided by √2?
    Because it is the perpendicular (Euclidean) distance from the point to the line A + I = 1, and the perpendicular distance from (I,A) to that line is |A + I − 1| / √2. Dropping the divisor just rescales the metric to a friendlier 0..1 range; the ordering of components is identical.
  • Is a component at (I=0.5, A=0.5) a problem?
    No — it sits exactly on the main sequence. The line is not only about the two endpoints; any component whose abstractness matches its stability is balanced. Half its types being contracts, with as many dependents as dependencies, is a normal domain-service shape.

Like the astronomers' main sequence it is named after: most healthy stars fall along one diagonal band on the brightness/temperature chart, and the oddballs off the band — giants and dwarfs — are the ones worth investigating. Being on the band isn't a law of nature, it's where well-behaved specimens cluster.

saying these in an interview costs you the question

  • Treating D as a hard quality gate ("every module must be under 0.2"), which drives interface-for-interface's-sake
  • Forgetting the absolute value hides direction — you must inspect A + I − 1's sign to know which corner you're drifting toward
  • Claiming every component must be at an endpoint of the line, rather than anywhere on it
  • Assuming a high D is automatically a defect, ignoring whether the component is volatile at all
  • Confusing the main sequence with the Stable Dependencies Principle — SDP is about the direction of edges between components, the main sequence is about one component's own A/I balance

context