You are defining the release strategy for an organisation's internal shared components. What policies and mechanisms make the Reuse/Release Equivalence Principle (REP) actually hold at scale, and when would you deliberately not release a reusable piece of code?
answer
- owner + charter + declared public API
- immutable versions, retained, semver-gated in CI
- deprecation window + support/patch SLA
- dependency inventory + adoption dashboard + upgrade bots
- don't publish: one consumer, churning API, deliberate duplication
basics
~20 sGive each component an owner, a charter, an immutable versioned artifact in a registry, a semver policy over a declared public API, per-release notes, and a deprecation/support window. Skip releasing code that has no cross-boundary consumer or is still churning heavily.
solid answer
~50 sREP holds at scale only when release is a supported organisational capability, not a per-team improvisation. The policy set: a named owner and written charter per component; a declared **public API surface** distinct from internals; **semantic versioning** applied to that surface, with immutable versions in a shared registry retaining old releases; machine-readable release notes; a deprecation policy with mark-then-remove and a stated support window; and security/patch obligations. The mechanisms: paved-road release pipelines, API-compatibility checks in CI, dependency inventory so you know every consumer, adoption dashboards, and automated upgrade PRs to stop version drift. Deliberately *don't* release when there is no cross-boundary consumer (premature abstraction, use-it-once code), when the API is still churning (early-stage code favours developability over reusability), or when a single-version monorepo policy already supplies atomic consumption. Publishing anyway buys ceremony and a compatibility promise you can't yet keep.
go deeper
List the basics: each shared library needs an owner, a version number, a place to publish it, and release notes so users know what changed.
Add semantic versioning over a defined public API, immutable retained versions, a deprecation policy, and the practical need for automated upgrades so consumers don't drift.
Cover enforcement — CI API-compatibility gates, boundary/visibility rules, dependency inventory, adoption metrics — plus the explicit cases where you should not publish (single consumer, churning API, deliberate duplication).
Frame it as who absorbs the cost of change, differentiate policy tiers by blast radius, define promotion and retirement criteria, budget the platform investment (registry, pipelines, codemods, inventory), and name the conditions for moving a component between the versioned and single-version models.
## The principle, at organisational scale **REP — Reuse/Release Equivalence Principle**: *the granule of reuse is the granule of release.* At one team's scale this reads as "package it and version it". At org scale it means: **release is a product capability**, with policies, tooling and obligations attached. Most REP failures in large organisations are not conceptual — everyone agrees reuse should be versioned — they are *operational*: nobody owns the component, releases are hand-cut, nobody knows who the consumers are, and old versions rot in production. ## The policy layer 1. **Ownership.** Every published component has a named owning team, not an individual. No owner ⇒ no component; either assign one or fold the code back into its single consumer. 2. **Charter.** A short written statement of what the component is for, what belongs in it, and explicitly what doesn't. This is the operational defence of the *theme* REP implies, and the thing you cite in review when someone adds an unrelated helper. 3. **Declared public API surface.** State precisely which types/functions carry the compatibility promise; everything else is internal and may change freely. Without this line, semver is meaningless because any observable change is arguably breaking (Hyrum's law: with enough consumers, every observable behaviour becomes someone's dependency). 4. **Versioning policy.** Usually **semantic versioning** on the declared surface: MAJOR = incompatible change, MINOR = compatible addition, PATCH = compatible fix. Define what counts as breaking (signature changes, behaviour changes, tightened validation, removed config keys, changed defaults, dropped runtime/platform support). 5. **Immutability and retention.** Published versions are never re-published with different bytes; old versions remain resolvable for at least the support window — otherwise pinning is a lie and old builds stop reproducing. 6. **Release notes.** Per version, consumer-facing: added / fixed / breaking / migration steps. Machine-readable where possible so tooling can surface it in upgrade PRs. 7. **Deprecation and support policy.** Mark-then-remove: an API is annotated deprecated with a replacement and a removal version/date, survives at least one MAJOR or a fixed calendar window, and only then is removed. Publish the support matrix (which MAJOR lines get fixes, for how long). 8. **Security obligation.** Committing to publish means committing to patch. Define severity-based SLAs, whether fixes are backported to supported lines, and how consumers are notified. 9. **Promotion criteria.** A written bar for "this code graduates from internal helper to published component": ≥N cross-boundary consumers, stable API for ≥N months, an owner willing to take the obligations, docs and tests. This is the single most effective brake on premature publishing. ## The mechanism layer - **Paved-road pipeline.** One templated release path (build → test → API check → publish → notes → announce). If cutting a release is manual and painful, teams route around REP by copy-pasting. - **API compatibility checking in CI.** Automated comparison of the public surface against the last release; fail the build if a MINOR/PATCH removes or changes it. This turns semver from a promise into an enforced invariant. - **Dependency inventory.** A build-graph or SBOM-derived index of *who depends on what, at which version*. Without it you cannot plan a deprecation, assess a CVE's blast radius, or know when it's safe to remove an API. - **Adoption dashboards.** Version distribution per component — the metric that tells you whether the ecosystem is healthy or drifting. - **Automated upgrade PRs.** Bots that open and (for green PATCH/MINOR) auto-merge upgrades. Version drift is REP's dominant real-world failure mode: everyone pins, nobody upgrades, and a critical fix reaches 20% of consumers. - **Boundary enforcement.** Visibility rules / dependency linting so consumers cannot reach past the public API into internals. - **Registry with retention and provenance.** Old versions available, artifacts signed, source-to-artifact traceability. ## When *not* to release — deliberate REP exceptions REP applies to code intended for reuse *across a release boundary*. Publishing has real cost; these are legitimate reasons to abstain: - **No cross-boundary consumer yet.** One user means it isn't reuse, it's a folder. Wait for genuine demand ("rule of three"-style patience) rather than designing an API from one example — premature abstraction is harder to unwind than duplication. - **API still churning.** Martin's own maturity argument: early components optimise for **developability**; publishing freezes an API you don't yet understand and commits you to migrating consumers through your own learning curve. Let it stabilise inside one boundary first. - **Single-version monorepo policy already in force.** If all consumers build at HEAD atomically, per-component versioning adds ceremony without removing skew that doesn't exist. Keep boundaries, ownership and changelogs; skip the registry. - **Deliberate duplication.** Two similar-looking pieces of code that will evolve for different reasons (different bounded contexts) should stay duplicated. Sharing them creates coupling between contexts that must later be untangled — the shared-library-as-hidden-coupling failure. - **No owner willing to take the obligations.** Publishing without patch/deprecation ownership creates a liability: consumers depend on something nobody maintains. - **Volatile-by-nature glue.** Configuration, environment-specific wiring and thin orchestration usually shouldn't be published; they change per deployment, not per release. ## Failure modes to design against | Failure | Signal | Countermeasure | |---|---|---| | Version drift | Wide version spread in adoption dashboard | Automated upgrade PRs, support windows that expire old lines | | Semver corrosion (breaking change shipped as PATCH) | Consumer breakage after "safe" upgrades | CI API-compatibility gate | | Grab-bag drift | Unrelated code appearing in a component | Charter + review + consumer-usage review | | Unmaintained published components | Open issues, no releases, unknown owner | Ownership registry, periodic component review, archive/deprecate path | | Reach-in coupling | Consumers importing internals | Visibility rules, dependency lint | | Premature publishing | Many MAJOR releases in the first year | Promotion criteria before publishing | | Undeletable APIs | Removal always blocked by 'someone might use it' | Dependency inventory turns 'might' into a list | ## The strategic framing REP at scale is really a statement about **who absorbs the cost of change**. Independently versioned releases push cost onto consumers (upgrade toil, drift, diamond conflicts) and buy them autonomy. Single-version consumption pushes cost onto producers (fix all call sites) and buys uniformity. No release at all pushes cost into the future (duplication, or a painful extraction later). A principal-level answer names that trade explicitly, chooses per component class rather than issuing one blanket rule, and defines the exit criteria for moving a component between models.
- Version drift is the most common way REP degrades in practice. How do you actually keep an ecosystem current?Make upgrading cheaper than not upgrading. Automated upgrade PRs with green-build auto-merge for PATCH/MINOR; machine-readable release notes surfaced in the PR; codemods shipped with breaking changes so MAJOR migrations are mostly mechanical; adoption dashboards with per-team visibility; support windows that expire old MAJOR lines so staying behind eventually costs support; and, for security, a severity-based SLA with proactive notification driven off a dependency inventory.
- How do you decide whether a piece of code should be promoted to a published shared component?Set explicit promotion criteria and apply them: at least two or three genuine cross-boundary consumers (not hypothetical), an API that hasn't changed shape in some months, an owning team that accepts patch and deprecation obligations, tests and docs, and a charter describing scope. Before that bar is met, duplication is usually cheaper than a premature abstraction, because the wrong shared API couples consumers and is expensive to change once released.
- What is the risk of extracting a shared component from two bounded contexts that happen to have similar code today?You couple contexts that should evolve independently. The two copies looked identical by coincidence, not by shared reason to change; once shared, every divergent requirement becomes a parameter, a flag or a conditional in the shared component, and the component accumulates the union of both contexts' needs. This violates the Common Closure Principle's guidance to separate code that changes for different reasons, and the untangling later is more expensive than the duplication would have been.
Publishing a shared component is like putting a product on the market rather than lending a neighbour a tool. Once it's on the shelf you owe buyers a stable SKU, a spec sheet, recall handling, spare parts for a stated number of years, and notice before you discontinue it. Organisations get into trouble by putting things on the shelf without accepting any of those obligations.
saying these in an interview costs you the question
- "Publish everything reusable" — publishing carries ownership, compatibility, patching and deprecation obligations; without an owner it creates a liability.
- "Semantic versioning solves compatibility" — semver is only a claim unless the public surface is declared and compatibility is checked in CI.
- "Consumers can upgrade whenever they like" — unbounded support means old lines never die and every removal is blocked forever; you need support windows.
- "We'll find the consumers when we need to" — without a dependency inventory you can't plan deprecations or assess a vulnerability's blast radius.
- "Duplication is always a defect to be extracted" — code that merely looks alike but changes for different reasons should stay duplicated.
- "REP means never delete anything from a shared component" — REP requires a communicated release process, which explicitly includes a deprecation and removal path.
- "One release policy for the whole company" — obligations should scale with a component's blast radius and consumer count, not be uniform.