Case file A3

Stable Dependencies Principle

Volatile depends on stable, never the reverse.

Dependencies have a direction. Volatile code depends on stable code. Never the reverse. Reverse the arrow and the volatile core destabilises everything that imports it.

ByAdam LewisPublished3 May 2026Revised13 Oct 2026Reading12 minVersionv1.2ConfidenceHigh
§0b

Opinion

Most architecture diagrams I've been handed in my career are lies because they redraw the import graph the team wishes it had rather than the one the build is compiling. The Stable Dependencies Principle is the rule that puts the diagram and the graph in the same direction. When a heavily-imported file imports a barely-imported one, that barely-imported file's next change ripples through the graph in ways the team can't see until CI goes red.1

The reason it gets confused with A11 Dependency Inversion is that they live in adjacent rooms. DIP is a class-level rule about what kind of thing the code depends on: an abstraction rather than a concrete. SDP is a component-level rule about which direction the import points: from less-stable to more-stable. They compose: SDP plus the Stable Abstractions Principle gives Martin's “stable abstractions at every scale” argument, which is DIP scaled to packages.2 Different scales and different objects in the same family.

The lever that makes SDP visible is the import graph. Without it the rule is a vibe. With it it is a build failure. A2 Three-Tier Hoisting is already SDP under another name: the generic/ → product/ → module gradient is a stability gradient by construction. The upward-only import rule is exactly “volatile depends on stable” expressed in file paths.

Copy a note and link

Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.

Volatile depends on stable, never the reverse. Read every import as an arrow: arrows point from less-stable code (high I = Ce / (Ca + Ce)) toward more-stable code (low I). When a stable file grows an import on a volatile one, invert the dependency through an interface or move the volatile thing.

/tenet/stable-dependencies-principle/A3
Copy for paste
§0c

AI eyes only

Rule: volatile depends on stable. Imports flow toward stability. Never the reverse.

Reject: stable libraries importing from volatile features. Reject: utility files importing domain types. Reject: lib/generic/ importing from lib/product/.

Generate: when a stable file needs a volatile detail, invert via interface. The volatile side implements the interface; the stable side depends on the abstraction.

Diagnostic: list the imports of the file you are editing. If any target changes more often than this file, invert the dependency before you merge.

§0d

Why?

  • The import graph is no longer a vibe. With SDP enforced, the direction of every edge is a decision the team can read in the build, not an emergent property of who edited which file last.
  • Stability becomes countable: I = Ce / (Ca + Ce). Reviewers stop arguing about whether a file “feels stable” and start asking the metric. The conversation shrinks to the data.
  • Caps the blast radius of a change to a stable file. When a low-I file changes, the team knows by construction which high-I leaves will recompile and which won't. Surprises in CI become almost rare.
  • Pairs cleanly with A11 Dependency Inversion: SDP picks the direction; DIP picks the shape (abstraction over concrete). Together they give Martin's “stable abstractions at every scale” argument its full form.
  • Falls out of A2 Three-Tier Hoisting for free. The generic/ → product/ → module gradient is a stability gradient by construction; A2's upward-only import rule is SDP in folder form.
  • Surfaces the deprecation conversation early. A file whose I climbs over time is one whose importers are moving away from it: a measurable signal that a v2 is overdue, without waiting for the team to notice in retro.
  • Coding agents reach for sideways imports and inheritance because the training data is full of both. The upward-only direction rule pushes them through composition or abstraction: the same lever that protects human reviewers also protects the agent from itself.
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

Robert C. Martin published the metrics that anchor SDP in “OO Design Quality Metrics: An Analysis of Dependencies” in 1994.5 Afferent coupling (Ca), efferent coupling (Ce), Instability (I = Ce / (Ca + Ce)), Abstractness (A) and the Distance from the Main Sequence (D) all originate there. The 1994 paper was a quantitative argument: package-level health could be measured the way function-level complexity could be measured. The measurements predicted where the next regression would surface.

Martin formalised SDP in the 1996 C++ Report piece “Granularity” and in the package-design chapters of Agile Software Development: Principles, Patterns, and Practices (2002). The single load-bearing line: “Depend in the direction of stability.”6 The chapter pairs SDP with its sibling SAP (Stable Abstractions Principle) so that the most-stable packages are also the most abstract: otherwise the stable parts of the system become the rigid parts.

The book-length restatement is Chapter 14 of Clean Architecture (Pearson, 2017).1 Martin pairs SDP with the Acyclic Dependencies Principle (ADP) and SAP into a single Component Coupling story: the package graph must be a DAG, the edges must point toward stability and the most-stable packages must be the most abstract. ADP catches the cycles; SDP catches the bad direction; SAP catches the rigid stable thing.

The instinct is older than the metric. Larry Constantine's coupling hierarchy (1968, published 1974 with Stevens and Myers in “Structured Design”) named the direction of coupling decades before Martin gave the package version a number.7 Meilir Page-Jones's connascence taxonomy (1992, 1996) gave the same instinct a more granular vocabulary: connascence of name is weak and local, connascence of identity is strong and dynamic and the rule is to push every coupling toward the weaker, more local end, which, at the package level, is exactly what SDP's direction rule produces.8

§2

Quotes

Depend in the direction of stability.

Robert C. Martin · Clean Architecture (2017), Ch. 14

This metric has the range [0,1]. I=0 indicates a maximally stable category. I=1 indicates a maximally instable category.

Robert C. Martin · OO Design Quality Metrics (1994)

With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.

Hyrum Wright · Hyrum's Law

Complexity is caused by two things: dependencies and obscurity.

John Ousterhout · A Philosophy of Software Design (2018)
§3

Evidence

16 external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.

  1. 01
    Robert C. Martin · 1994
    The paper that defined Ca, Ce, I, A, and D. Argued package-level health is countable, not subjective. The metrical anchor for SDP and SAP both.
  2. 02
    Robert C. Martin · 2017
    The book-length restatement. Pairs SDP with the Acyclic Dependencies Principle and the Stable Abstractions Principle into a single Component Coupling story. Argues the package graph must be a DAG with edges pointing toward stability.
  3. 03
    Robert C. Martin · 2002 onwards
    Martin's online compendium of SOLID and the package-design principles. The single page where SDP, SAP and ADP are listed alongside SOLID: useful for showing they belong in the same family.
  4. 04
    WikiWikiWeb contributors · 2003 onwards
    The community wiki entry that distils SDP into one paragraph and links to its sibling rules. Useful as a beginner-readable pointer. The discussion thread captures the SDP-vs-DIP confusion in real time.
  5. 05
    Agile in a Flash · 2009
    Cohort summary of SDP + SAP + ADP for the Agile-in-a-Flash card series. The single-card format squeezes the principle onto an index card. Useful for the elevator-pitch version.
  1. 06
    Randy Coulman · 2014
    Two-part walkthrough of Martin's component principles with worked examples. Part 2 covers SDP and SAP specifically. Readable companion to the 2017 book chapter.
  2. 07
    Principles Wiki · 2024
    Index of every Martin principle in one place with cross-links. Useful for showing readers where SDP sits inside the larger Martin canon: between the SOLID class-level rules and the package-cohesion rules (REP, CCP, CRP).
  3. 08
    Vlad Khononov · 2021
    Modern restatement that grounds package coupling in DDD's subdomain types. Qualifies SDP by saying stability is a property of the subdomain (core, supporting, generic), not just of the package.
  4. 09
    John Ousterhout · 2018, 2021
    Argues complexity is the master metric, with dependencies and obscurity as its two causes. The reply: SDP handles the dependency-direction half; module depth handles the interface-shape half. Both rules apply.
  5. 10
    Hyrum Wright · 2010s
    “With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviours of your system will be depended on by somebody.” The strongest steelman: stability is a social fact the metric lags.
  6. 11
    Wikipedia contributors · 2024
    Index of the six Martin package principles (REP, CCP, CRP, ADP, SDP, SAP). Useful one-paragraph summaries plus the canonical reference list. The entry point for readers new to the family.
  7. 12
    Wikipedia contributors · 2024
    Connascence is the more granular sibling vocabulary. Qualifies SDP by saying direction-of-dependency is one coupling axis among nine (Page-Jones). The rule justifies itself most when paired with locality and degree.
  8. 13
    connascence.io contributors · 2018 onwards
    Practitioner reference for the nine forms and three properties. Frames package coupling as one part of a larger coupling story. The case file uses connascence as the formal vocabulary alongside SDP's direction rule.
  9. 14
    David Parnas · 1972
    The earlier origin. Parnas argued module decomposition should hide design decisions behind interfaces. Dependencies should point toward the hidden part, not the volatile part. Forty years before SDP gave the rule a number.
  10. 15
    Khalil Stemmler · 2020
    Modern primer that places SDP inside the larger coupling story. Useful for readers who arrive thinking “coupling” means one thing; Stemmler shows it splits into a taxonomy and SDP is one rule inside that taxonomy.
  11. 16
    Martin Fowler · 2014
    The disambiguation aid. Fowler's DIP write-up frames what DIP is and is not. Useful for the case file's SDP-vs-DIP callout because it shows the two rules as siblings, not synonyms.

Sixteen sources, three stances. The supporters cluster on Martin's component-coupling canon: the 1994 metrics paper, the 2017 Clean Architecture chapter, plus the wiki and Agile-in-a-Flash distillations. The qualifiers further down hold the harder reading: stability is one coupling axis among several. The metric is only useful when the team agrees what “stable” means in their context. The opposers hold the steelman the reply has to address: any sufficiently-used interface becomes its own promise, regardless of which way the arrows point.

§4

Examples

Viewing: TypeScript.
Avoid

Before lib/generic/ imports from lib/product/. The arrow points the wrong way.

Filelib/generic/string/to-slug.ts
import { Tenet } from "@/lib/product/tenet/tenet.types";

export function toSlug(tenet: Tenet): string {
  return tenet.title.toLowerCase().replace(/\s+/g, "-");
}

// any Tenet field rename now ripples through every to-slug caller.
Prefer

After generic takes a primitive. The Tenet-aware caller composes the two.

Filelib/generic/string/to-slug.ts
lib/generic/string/to-slug.tsno upward import
export function toSlug(value: string): string {
  return value.toLowerCase().replace(/\s+/g, "-");
}
lib/product/tenet/get-tenet-slug.tscomposes the two
import { toSlug } from "@/lib/generic/string/to-slug";
export const getTenetSlug = (tenet: Tenet): string => toSlug(tenet.title);
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
import/no-restricted-pathseslint-plugin-importany import edge that points from a stable tier to a volatile tier: a generic file growing an import on a product file, or a product file growing an import on an app route.
import/no-cycleeslint-plugin-importimport cycles between any two packages: the symptom that two files are at the same stability tier and have grown into each other rather than past each other.
validate / metrics outputdependency-cruiserthe same upward-only contract as a standalone CI step, plus per-package Ca/Ce/I/A/D metrics: the numbers SDP is built on, machine-readable.
import/no-relative-packageseslint-plugin-importrelative imports across package boundaries: the import shape that defeats SDP by routing around the public surface.
eslint.config.mjsconfiguration snippet
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';

export default defineConfig({
  files: ['**/*.{ts,tsx}'],
  plugins: { import: importPlugin },
  rules: {
    'import/no-restricted-paths': ['error', {
      zones: [
        {
          target: './src/lib/generic',
          from: ['./src/lib/product', './src/components', './src/app'],
          message: 'volatile depends on stable, never the reverse, generic is the most stable tier.',
        },
        {
          target: './src/lib/product',
          from: ['./src/components', './src/app'],
          message: 'product is more stable than the leaves; leaves depend on product, not the reverse.',
        },
      ],
    }],
    'import/no-cycle': ['error', { maxDepth: 10 }],
    'import/no-relative-packages': 'error',
  }
});
§4c

AI rules

File.cursor/rules/a3-stable-dependencies.mdc
---
alwaysApply: true
---

## Prickles A3: Stable Dependencies Principle

Make volatile code depend on stable code, never the reverse. Read every import edge as an arrow: it must point from less-stable code toward more-stable code.

Stability is countable: I = Ce / (Ca + Ce). High-I files are leaves (depend on lots, depended on by few). Low-I files are roots. Imports must point from high-I toward low-I.

Pair SDP with the Stable Abstractions Principle (SAP): the most-stable packages must also be the most abstract. Otherwise the stable parts of the system become the rigid parts.

Refuse to grow a stable file with an import on a volatile one. If you would, invert the dependency through an interface or move the volatile thing into a less-stable tier.

Repo layout, CI, and ESLint wiring for these paths live on /implementation, not repeated on every tenet.

§5

Counter-argument

Counter

The strongest steelman comes from John Ousterhout combined with Hyrum's Law.3 An interface with enough callers will be relied on for every observable property, documented or not, which means “stable” in practice is a consequence of usage rather than a property the rule can dictate. SDP says “volatile depends on stable” as if stability were an attribute of the file. In practice it is an attribute of the social contract around the file. The metric I = Ce / (Ca + Ce) is a proxy for that contract that misses the unwritten parts. The implication: enforcing SDP on a metric that lags the social reality can produce a folder structure that looks healthy and a team that still pays for every “stable” file's every change.

§6

Counter-argument retort

Reply

Hyrum's observation is correct, and SDP doesn't pretend otherwise. The reply is that SDP is the rule for what the team controls about stability (the import-graph direction) and not for what it doesn't. A file that's currently stable by the metric and unstable by the social contract is a deprecation problem rather than an architecture one. The next move is to publish a v2 surface and migrate the callers, exactly the cycle the Stable Abstractions Principle is here to support.4 SDP catches the bad direction and SAP catches the bad shape. Together they cover the part of stability the build can see.

The Ousterhout depth concern is real but lives one level down from this rule. SDP tells you which direction edges should point in the package graph. Module depth tells you how those packages should organise their internals.3 Both rules apply and neither replaces the other. A package with the right import direction and a shallow interface is still a leaky abstraction. A package with a deep interface and a wrong import direction is a time bomb. The team needs both metrics. The tools to read both metrics live in different files.

The genuine residue is the conflation with A11 Dependency Inversion. They're different rules at different scales: DIP is class-level (depend on abstractions, not concretes); SDP is package-level (depend on stable components, not volatile ones). The case file pins the distinction once so readers don't conflate them. SDP + SAP is closer to DIP scaled up, but it's still not the same rule: SDP is about direction, DIP is about shape.2 Both belong in the canon for the same reason F1 Single Responsibility (function-scale) and A4 Common Closure (package-scale) both belong: scale matters, so the same instinct earns separate rules at separate altitudes.

The novelty challenge, that this is just “import graph cleanliness” under a fancy name, misses what the metric does. Without I and Ca/Ce, “cleanliness” is a vibe. With it, “the metric drifted from 0.1 to 0.4 this quarter on the auth package” is a sentence the team can act on. The metric earns the rule.

§7

Notes

  1. [1]Robert C. Martin: Clean Architecture (Pearson, 2017), Ch. 14 “Component Coupling”. The single load-bearing chapter for SDP, SAP, and ADP together: the Component Coupling triad.
  2. [2]Martin Fowler: “DIP in the Wild” (martinfowler.com, 2014). Fowler's account of what DIP is and is not: useful for showing that SDP and DIP are siblings (different scale, different object) rather than the same rule under different names.
  3. [3]John Ousterhout: A Philosophy of Software Design (2018, 2nd ed. 2021). The strongest steelman for the “module depth over direction” counter; SDP's reply is that depth lives one level down from this rule and both apply.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-10-13