skip to content

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%

answer

  1. Depending ≠ calling
  2. Release coupling: re-test + redeploy for changes you don't use
  3. Transitive closure → version diamonds
  4. CVE scanners see presence, not reachability
  5. Monorepo/tree-shaking weakens but doesn't remove the cost

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.

solid answer

~50 s

The argument confuses "calling" with "depending on". A component is the unit of release, so the dependency edge carries far more than a call graph. Concretely: (1) release coupling — every version bump, including ones caused entirely by the unused half, forces you to decide, re-integrate, re-run tests, re-certify and redeploy, or to knowingly diverge; (2) transitive closure — the unused subsystem drags in its own libraries, enlarging your dependency tree; (3) version-conflict surface — those extra transitives collide with other components' pins, producing unresolvable diamonds; (4) security and compliance — a CVE in unused code is still reported against your build and must be remediated or formally accepted; (5) build and runtime weight — longer builds, bigger images/bundles, slower cold starts; (6) cognitive and API surface for every consumer. Any one of these can dominate; together they are why CRP exists.

go deeper

for a junior

Name two or three concrete costs — extra transitive libraries, forced upgrades/re-testing, security findings — and state that a component is the unit of release.

for a middle

Cover the full list and explain the mechanism behind each; distinguish presence-based scanning from reachability.

for a senior

Quantify: irrelevant-release count, transitive subtree size, CVE count; and weigh against the cost of publishing another artifact.

for a principal

Position it as dependency-hygiene policy: artifact-count budgets, SBOM and supply-chain posture, deprecation/migration windows, and where monorepo atomic builds legitimately relax the constraint.

## Restating the disagreement precisely The colleague's model is: *dependency = the set of code I execute*. The correct model is: *dependency = coupling to another component's release cycle and dependency closure*. Once you accept the second model, every item below follows mechanically. ## 1. Release coupling (the original motivation) Martin's motivating scenario: component **B** contains classes you use and classes you don't. B releases version 3.1 because of a change in the part you never touch. You now face a forced choice: - **Upgrade:** rebuild, re-run the test suite, possibly re-run integration/e2e, possibly re-certify (in regulated environments: re-validate, re-file), and redeploy. Non-zero engineering time for zero functional benefit. - **Don't upgrade:** pin to 3.0 and start accruing drift. The next security patch will be published on 3.2, on top of the changes you avoided, so you pay the deferred cost with interest — and meanwhile other components in your graph may already demand 3.1+. Neither option is free. This is the cost CRP is designed to eliminate: a consumer should only be exposed to releases of things it actually uses. ## 2. Transitive dependency closure The unused half brings its own dependencies. A component you took for a string helper may drag in an XML parser, a template engine, a reflection utility, a metrics client. Effects: larger download, larger lockfile, more supply-chain surface, more license obligations to review, more entries your SBOM (software bill of materials) must list. ## 3. Version-conflict / diamond surface Every extra transitive is another node that can conflict. Two of your components want incompatible versions of a library that neither of you asked for — a library that only exists in your tree because of classes you never call. In ecosystems with a single flat resolution (Java classpath, Go modules, Python site-packages) this can be genuinely unresolvable; in ecosystems allowing multiple copies (npm), it inflates size and can break identity checks across duplicate copies. ## 4. Security, licensing and compliance A vulnerability scanner reports on what is *present*, not on what is *reachable* (reachability analysis exists but is neither universal nor fully trusted). So a CVE in the unused subsystem becomes your ticket, your SLA clock, your upgrade. Similarly a copyleft or restricted license in a transitive of the unused half becomes your legal review. ## 5. Build-time and runtime weight More code to fetch, verify, compile against and package. Bigger container images and slower pulls. Bigger client bundles (in browsers, directly user-visible latency). More classes to load and verify at startup, which matters for serverless/short-lived processes. Slower IDE indexing. ## 6. Human/API surface Every consumer's autocomplete, documentation and mental model now contain irrelevant concepts. New engineers cannot tell which parts of the component are "for them". Grab-bag components also attract more junk over time (broken-windows effect), which compounds every cost above. ## 7. Coupling to the wrong team's cadence Organisationally, depending on a component means depending on its owning team's release discipline, deprecation policy and support level — for the whole artifact, not the slice you use. ## The honest counter-arguments (know these too) A good candidate acknowledges when the colleague is *partly* right: - **Internal monorepo, one build, one deploy unit:** if consumers and provider are built and released atomically from one repository, release coupling largely disappears and only build-time and cognitive costs remain. CRP pressure is genuinely lower there. - **Dead-code elimination / tree shaking:** in some ecosystems the *shipped bytes* of unused code can be removed, addressing cost 5 — but not costs 1–4, and not when reflection or dynamic loading defeats the analysis. - **Splitting is not free:** more artifacts mean more versioning and release coordination. The rebuttal is not "always split"; it is "the cost is real, so weigh it deliberately." ## How to make the argument stick Use evidence, not principle: show the last N releases of the component and how many were irrelevant to your service; show the transitive tree contributed only by the unused half; show the open CVEs in that subtree. That converts an abstract principle into a number.

  • In which environment is your colleague's argument strongest?
    A monorepo with atomic builds and a single deployment unit, where provider and consumer are always released together. There is no independent release cycle to absorb, so release coupling nearly vanishes and only build time, cognitive load and sometimes artifact size remain.
  • Does vulnerability-reachability analysis fully neutralise the security argument?
    No. Reachability tooling can down-rank a CVE in unused code, but it is not universally available, struggles with reflection, dynamic loading and configuration-driven wiring, and many compliance regimes still require remediation or a documented exception based on presence in the SBOM.
  • How would you quantify the cost to justify a split?
    Count, over the last release history, how many releases of the component were irrelevant to each consumer and the engineering hours spent absorbing them; measure the transitive subtree contributed solely by the unused portion, its CVE count and its size contribution; then compare against the estimated cost of maintaining an extra published artifact.

A shared apartment lease. You only use your bedroom, but you're on the hook for the whole flat: when someone else floods the kitchen, your lease is renegotiated, your insurer is notified, and you can't move out of just your room.

saying these in an interview costs you the question

  • Treating a dependency as equivalent to a call graph.
  • Assuming compilers or linkers strip unused library classes by default in every ecosystem.
  • Assuming a CVE in unused code can simply be ignored — most scanners and audits are presence-based.
  • Overshooting the rebuttal into "therefore always split into micro-packages", ignoring the real cost of extra artifacts.
  • Failing to concede the monorepo/atomic-release case where the cost genuinely is small.

context