skip to content

Package and Component Principles

Once classes are grouped into releasable components, a second set of principles decides what belongs together and which way component dependencies may point. This is where interviewers move from class design to module and build-graph design.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

What does the Acyclic Dependencies Principle (ADP) state, and which team failure mode — nicknamed "the morning-after syndrome" — does it prevent?

level: juniorimportance: must knowfreq 55%

answer

  1. No cycles in the component graph = DAG
  2. Morning-after syndrome: overnight breakage
  3. Release isolation: depend on versions, not working copies
  4. Cycle = components must ship in lockstep
  5. Classes may cycle; components may not

basics

~20 s

ADP says: allow no cycles in the dependency graph between components (releasable units like packages, modules, or libraries). Cycles cause the "morning-after syndrome": someone edits code you depend on overnight, your work breaks, and because dependencies loop, nobody can ever stabilize.

solid answer

~50 s

ADP: "Allow no cycles in the component dependency graph." A component is a unit you can release and deploy on its own — a package, module, library, JAR/DLL/npm package. Draw an arrow A → B whenever code in A references code in B; that graph must be a directed acyclic graph (DAG). The morning-after syndrome is the failure it prevents: when components depend on each other in loops, any change can break anyone, so developers arrive each morning to find yesterday's working code broken by someone else's overnight edits — chronically, as the system grows. A DAG lets each team build against a *released, versioned* copy of its dependencies and choose when to absorb a new version. In a cycle that is impossible: A cannot cut a stable release without B, and B needs A. Cycles also fuse the whole loop into one thing that must be compiled, tested, and deployed together.

go deeper

for a junior

State the principle, define "component" as a releasable unit, and give the morning-after story as the motivation.

for a middle

Add the concrete costs — lockstep releases, transitive build/test blowup, loss of reuse — and note cycles usually appear indirectly.

for a senior

Frame ADP as enabling release isolation and versioned dependencies; distinguish compile-time from runtime cycles; mention CI enforcement tooling.

for a principal

Position ADP among the component coupling principles (with SDP/SAP), tie acyclicity to team autonomy and deployment topology, and discuss the organizational cost of a component graph nobody owns.

## Vocabulary first - **Component**: the smallest thing you can *release* and *deploy* independently — a package, module, library (JAR, DLL, gem, wheel, npm package), or at larger grain a deployable service. ADP is about components, **not** individual classes. - **Dependency**: component `A` depends on `B` if code in `A` refers to code in `B` (imports, calls, extends its types, links against it). Draw an arrow `A → B`. - **Component dependency graph**: all components as nodes, all those arrows as edges. - **Cycle**: a path of arrows that returns to where it started — `A → B → A`, or longer and sneakier: `A → B → C → D → A`. - **DAG (directed acyclic graph)**: a directed graph with no cycles. ADP restated: *the component dependency graph must be a DAG.* ## The principle > **Allow no cycles in the component dependency graph.** It is one of the three *component coupling* principles (with SDP, the Stable Dependencies Principle, and SAP, the Stable Abstractions Principle), popularized by Robert C. Martin. Cohesion principles (REP/CCP/CRP) decide *what goes in* a component; coupling principles decide *how components may point at each other*. ## The morning-after syndrome The origin story: in a growing team, everyone edits shared code. You go home with green tests. Overnight, someone changes a module you depend on. Next morning your code no longer builds or passes. You fix it — and break someone else. Beyond a certain size the team spends more time chasing each other's changes than delivering. Nobody can point at a "good" version of anything, because a good version of `A` requires a good version of `B`, which requires a good version of `A`. **The classic cure is the weekly build + release isolation.** Teams stop consuming each other's *working copies* and start consuming each other's *released, version-numbered artifacts*: "I depend on `Persistence 1.4.2`." When a new version ships, I read its release notes and migrate **when I choose**. My world stops moving under me. That decoupling is only possible when the graph is acyclic: - In a DAG, a component only needs its dependencies to be releasable to be releasable itself. You can always find leaf components with no dependencies, release them, then release their dependents, and so on. - In a cycle, `A` can't be validated without `B`, and `B` can't be validated without `A`. They are effectively **one component wearing several hats** — a "big ball of mud" with directory separators pretending to be structure. ## What a cycle actually costs 1. **Release lockstep** — every component in the loop must be versioned and shipped together. Independent deployability, the whole point of componentization, disappears. 2. **Build/test blowup** — to test `A` you must compile `B`, `C`, `D`… and transitively everything they reach. Test suites get slower and less isolated; unit tests drag in half the system. 3. **Change amplification** — a change in any member of the cycle can ripple to all the others, in both directions. Reasoning becomes global rather than local. 4. **No reuse** — you cannot take `A` into another product without dragging the entire loop. 5. **Ownership fog** — two teams owning two components in a cycle are permanently coupled to each other's schedules. ## Important nuances - **Cycles between classes inside one component are fine.** Mutual recursion, back-references, observer callbacks — ADP is silent on those; they ship together anyway. The principle constrains the *component* graph. - **Cycles are usually created by accident and indirectly.** Nobody writes `A → B → A` on purpose. Someone adds one innocent import (`Utilities` needs one enum from `Reporting`) and a five-node cycle appears that no single reviewer saw. That is exactly why it must be checked by a tool in CI, not by eyeball. - **ADP is not "minimize dependencies".** A DAG can be dense. ADP only forbids *loops* in the direction of dependency. - **Build-time vs runtime.** ADP is about *source/compile/deploy-time* dependency. Runtime call graphs may legitimately loop back via callbacks, events, or dependency injection — indeed inverting a compile-time arrow while keeping the runtime call direction is the standard fix (see DIP). ## How to check it Any reachability/strongly-connected-component analysis over the import graph: `jdeps`/ArchUnit/Spring Modulith (JVM), `dependency-cruiser`/`madge` (JS/TS), `import-linter` (Python), `go list`/language-enforced acyclicity (Go forbids import cycles outright), NDepend (.NET). Make it a build-breaking rule, so the graph can never quietly regress.

  • If cycles are so damaging, why do they keep appearing in real codebases?
    Because they emerge indirectly: a single new import can close a loop several hops long that no reviewer can see in a diff. The graph is emergent, not designed, so it needs an automated check in CI rather than discipline alone.
  • Does ADP forbid two classes in the same package referring to each other?
    No. ADP constrains the graph between components (independently releasable units). Inside a component, mutual references are normal — the whole component ships as one unit anyway.

Think of components as suppliers in a manufacturing chain. If the engine plant needs finished cars to build engines, and the car plant needs engines to build cars, neither can ever ship a first unit — they must retool as one giant factory. A one-directional supply chain lets each plant certify a batch, stamp a version on it, and let the next plant pull it when ready.

saying these in an interview costs you the question

  • Saying ADP means "reduce the number of dependencies" — it forbids loops, not density.
  • Claiming any mutual reference between two classes violates ADP.
  • Believing cycles are always direct A ↔ B; most real ones are long and indirect.
  • Thinking a cycle is harmless "because it compiles" — the cost is in release, test isolation, and change ripple, not in the compiler.
  • Confusing runtime call cycles (often fine, e.g. callbacks) with compile-time dependency cycles.

context

open as a page

What does the Common Closure Principle (CCP) of component design state, and what problem does it prevent?

level: juniorimportance: must knowfreq 45%

basics

~20 s

CCP says: put into one component the classes that change for the same reasons and at the same times. Then a typical change touches one component instead of many, so you rebuild, retest and redeploy less.

open as a page

What does the Common Reuse Principle (CRP), one of Robert C. Martin's component-cohesion principles, state, and what problem is it meant to prevent?

level: juniorimportance: must knowfreq 40%

basics

~10 s

CRP says classes that are used together should be packaged together, and classes that are not used together should not be. It keeps consumers from depending on, rebuilding, and redeploying code they never use.

open as a page

In component-level design metrics, what do Instability (I) and Abstractness (A) measure, and how is each computed?

level: juniorimportance: must knowfreq 42%

basics

~20 s

Instability I = outgoing dependencies / (outgoing + incoming). It says how easily a component can be forced to change. Abstractness A = abstract types / all types. It says how much of the component is interfaces rather than concrete code. Both run 0 to 1.

open as a page

What does the Reuse/Release Equivalence Principle (REP) state, and what does it require of a reusable component?

level: juniorimportance: must knowfreq 38%

basics

~20 s

REP says the granule of reuse is the granule of release: the unit you reuse must be the unit you release. A component has to be published as a versioned whole, with a version number and release notes, so consumers depend on a specific version instead of copying files.

open as a page

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%

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.

open as a page

What does the Stable Dependencies Principle (SDP) require, and what does "stable" mean for a software component?

level: juniorimportance: must knowfreq 38%

basics

~20 s

The Stable Dependencies Principle says a component should depend only on components that are more stable than it. "Stable" means hard to change — lots of other things depend on it — not "never changes".

open as a page

Components A, B and C form a dependency cycle: A → B → C → A. Describe the two standard techniques for breaking such a cycle and how each changes the graph.

level: middleimportance: must knowfreq 50%

basics

~20 s

Two options. (1) Apply the Dependency Inversion Principle: put an interface that C needs into C (or a place C already sees), and have A implement it — the arrow A → B → C → A becomes C ← A with no return edge. (2) Extract the shared code into a new component D that both A and C depend on.

open as a page

Why does organising code into layer-based packages (controllers, services, repositories, models) usually violate the Common Closure Principle, and what packaging does CCP favour instead?

level: middleimportance: must knowfreq 50%

basics

~20 s

With layer packages, adding one feature field forces edits in every layer package at once. CCP wants classes that change together in one component, so it favours grouping by feature or business capability, with layers inside each feature.

open as a page

How does the Common Reuse Principle (CRP) relate to the Interface Segregation Principle (ISP), and how is it different from the Common Closure Principle (CCP)?

level: middleimportance: must knowfreq 38%

basics

~20 s

CRP is ISP one level up: ISP says don't make a client depend on methods it doesn't use; CRP says don't make it depend on classes it doesn't use. CCP is different — it groups classes that change together, not classes used together.

open as a page

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%

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.

open as a page

Your team publishes a shared library called `common-utils` that contains HTTP retry helpers, date formatting, CSV parsing, and a feature-flag client, all versioned together. Judged against the Reuse/Release Equivalence Principle (REP), what is wrong and what would you change?

level: middleimportance: must knowfreq 34%

basics

~20 s

The component has no coherent theme: nobody reuses all four things together, yet everyone shares one version. Any change forces unrelated consumers to see new versions and possibly breaking bumps. Split it into themed, separately versioned components with their own release notes.

open as a page

How is the abstractness metric A = Na / Nc computed for a software component, and what do the values A = 0 and A = 1 mean in practice?

level: middleimportance: must knowfreq 45%

basics

~20 s

Count the component's abstract types (interfaces and abstract classes) as Na and all its types as Nc; A = Na/Nc, from 0 to 1. A = 0 means entirely concrete implementation; A = 1 means nothing but interfaces, no implementation at all.

open as a page

How is the instability metric I = Ce / (Ca + Ce) computed for a component, and how do you use it to check the Stable Dependencies Principle?

level: middleimportance: must knowfreq 34%

basics

~20 s

Count outgoing dependencies (Ce) and incoming ones (Ca), then I = Ce / (Ca + Ce). I = 0 means maximally stable, I = 1 maximally unstable. SDP holds when each component's I is greater than or equal to the I of everything it depends on.

open as a page

In Robert C. Martin's component-cohesion tension diagram, the Common Reuse Principle (CRP) pulls against the Reuse/Release Equivalence Principle (REP) and the Common Closure Principle (CCP). Explain the tension and how you would choose a position for a given project.

level: seniorimportance: must knowfreq 30%

basics

~20 s

REP and CCP make components bigger (easier to reuse and to change in one place); CRP makes them smaller (consumers shouldn't carry what they don't use). You can't maximise all three, so you pick a point: young projects lean CCP, mature shared libraries lean REP and CRP.

open as a page

A very stable component must call code that lives in a highly volatile component, which would violate the Stable Dependencies Principle. How do you fix the dependency direction without deleting the call?

level: seniorimportance: must knowfreq 30%

basics

~20 s

Invert the dependency: define the needed operation as an interface owned by (or extracted next to) the stable side, have the volatile component implement it, and inject the implementation at runtime. The compile-time arrow now points from volatile to stable.

open as a page

How do you derive a valid build-and-release order for a set of components from their dependency graph, and why does a single cycle make such an order impossible?

level: middleimportance: should knowfreq 40%

basics

~20 s

Topologically sort the dependency graph: build components with no unbuilt dependencies first, then their dependents, and so on. A cycle has no such starting point — each member waits on another — so no valid order exists and the whole loop must be built and released as one unit.

open as a page

A colleague argues that the Common Reuse Principle doesn't matter because "unused classes in a dependency cost nothing — we never call them." What concrete costs would you cite to rebut this?

level: middleimportance: should knowfreq 30%

basics

~20 s

Depending on a component means depending on its whole release: you inherit its transitive libraries, its version constraints, its vulnerabilities, and you must re-test and redeploy every time it releases — even for changes to code you never call.

open as a page

What is the "zone of pain" on the Abstractness/Instability graph, and when is a component sitting there actually acceptable?

level: middleimportance: should knowfreq 30%

basics

~20 s

The zone of pain is the corner near Instability=0 and Abstractness=0: a component that everything depends on and that contains only concrete code. Changing it is expensive and ripples everywhere. It's fine only if that component essentially never changes.

open as a page

How do the Stable Abstractions Principle (SAP) and the Stable Dependencies Principle (SDP) work together, and how does that combination express the Dependency Inversion Principle at the component level?

level: middleimportance: should knowfreq 32%

basics

~20 s

SDP says depend in the direction of stability — point dependencies at things that are hard to change. SAP says those stable things should be abstract. Put together: dependencies flow toward abstractions, which is exactly what the Dependency Inversion Principle asks for, applied to whole components.

open as a page

Under the Stable Dependencies Principle, does a component that is edited every day automatically count as unstable? Explain the difference between stability and volatility, and why it matters.

level: middleimportance: should knowfreq 20%

basics

~20 s

No. Stability measures how hard a component is to change (how many things depend on it), while volatility is how often it actually changes. A frequently-edited component with many dependents is stable and volatile at once — the dangerous combination.

open as a page

Dependency cycles usually appear indirectly, from one seemingly harmless import. How would you detect, prevent and govern cycles in a growing codebase?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Don't rely on code review — a loop can span five components and be invisible in a diff. Add an automated dependency check to CI that fails the build on any cycle (ArchUnit/Spring Modulith, dependency-cruiser, import-linter, jdeps, NDepend), fix violations immediately, and keep a documented, owned component map.

open as a page

How would you use evidence rather than intuition to find Common Closure Principle violations in an existing codebase, and what would you do with the findings?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Mine version-control history: find files that keep changing in the same commits but live in different components. That temporal coupling is direct evidence the boundary cuts across a real axis of change, so move or merge those parts.

open as a page

The Common Closure Principle and the Common Reuse Principle pull component boundaries in opposite directions. Explain that tension and how you decide where to land.

level: seniorimportance: should knowfreq 32%

basics

~20 s

CCP is inclusive - it pulls classes together so one change hits one component. CRP is exclusive - it splits out classes that consumers don't use, so nobody depends on code they don't need. You balance them, favouring CCP early and CRP once code is widely reused.

open as a page

You own a widely used shared library whose consumers each pull it in for only one of five unrelated feature areas. Walk through how you would apply the Common Reuse Principle to split it, and what new costs the split introduces.

level: seniorimportance: should knowfreq 32%

basics

~20 s

Measure which classes each consumer actually uses, cluster by observed joint usage, split into one artifact per cluster, and keep the old artifact as a thin aggregator that depends on the new ones so nothing breaks. New costs: more versions, releases, pipelines and coordination.

open as a page

A shared module measures Instability I = 0.1 and Abstractness A = 0.05, and it is modified in most releases. Walk through how you would move it back toward the main sequence.

level: seniorimportance: should knowfreq 24%

basics

~20 s

It is stable and concrete — many modules depend on it, it has no interfaces, and it keeps changing. Extract its interfaces into a new contract module that dependents import, leave the concrete code in an implementation module nobody depends on, and split it by reason to change.

open as a page

What is the "zone of uselessness" on the Abstractness/Instability graph, how do components end up there, and what do you do about them?

level: seniorimportance: should knowfreq 22%

basics

~20 s

It's the corner near Instability=1 and Abstractness=1: a component full of interfaces and abstract types that nothing depends on. The abstractions exist but nobody uses them, so the code is dead weight — usually leftovers or speculative design.

open as a page

Does a monorepo where every service is built and deployed from HEAD violate the Reuse/Release Equivalence Principle (REP)? How should REP be applied when producers and consumers share one repository?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Not necessarily. REP demands that reuse happen through a defined, identifiable release. In a monorepo built at HEAD, the commit itself is the release: one version for everything, atomic changes, no pinning. It satisfies REP's intent only if boundaries, ownership and change communication still exist.

open as a page

How does the Reuse/Release Equivalence Principle (REP) interact with the Common Closure Principle (CCP) and the Common Reuse Principle (CRP), and what goes wrong when a team over-applies REP?

level: seniorimportance: should knowfreq 26%

basics

~20 s

REP, CCP and CRP pull in different directions. REP and CCP push components larger (releasable, coherent, co-changing); CRP pushes smaller (don't force consumers to depend on things they don't use). You pick a spot on that triangle that suits the project's current stage, then revisit it.

open as a page

What is the "Main Sequence" in component design, how is the distance metric D = |A + I - 1| interpreted, and what are the Zone of Pain and Zone of Uselessness?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Plot abstractness A against instability I. The Main Sequence is the line A + I = 1 — the healthy balance. D = |A + I - 1| measures how far off it a component sits (0 = on the line). The two bad corners are stable-and-concrete (Zone of Pain) and abstract-with-no-dependents (Zone of Uselessness).

open as a page

showing 1–30 of 39