Skip to the content.

Every dependency is a loan, and the interest is paid in bytes and risk

Every dependency is a loan, and the interest is paid in bytes and risk

Adding a dependency feels free — one npm install and the problem is solved. But a dependency is a loan, and the principal (the feature you got) is the small part. The interest is paid later and forever: bytes shipped to every user, a larger security surface, a maintenance burden when it needs upgrading, and the day it breaks, is abandoned, or ships a change you did not want. None of that shows at install time, which is exactly why the decision gets made carelessly. Borrowing is often the right call — you should not reimplement a date library — but it is a call, and it should be made by weighing the interest before you sign, not discovering it after.

The visible install cost versus the ongoing interest of a dependency A small labelled "install: free" box, and a much larger stacked set of ongoing costs: bundle bytes, security surface, maintenance, breakage risk, transitive deps. install: "free" then… bundle bytes (every user) security surface + CVEs upgrade / maintenance breakage / abandonment + its transitive depseach with the same costs
Install is the visible, one-time cost. The interest — bytes, CVEs, upgrades, breakage, and every transitive dependency's version of the same — is ongoing.

The interest is mostly invisible at install

Weigh what a package actually costs before adding it. Bytes: it ships to every user, on every load, forever — a 40KB library for a 10-line need is a bad trade. Security: every dependency (and every dependency of that dependency) is attack surface and a future CVE to patch. Maintenance: it must be upgraded, and majors break. Breakage: it can be abandoned or hijacked. Before installing, do the napkin check:

# what am I really borrowing?
npx bundlephobia left-pad     # bytes added, min+gzip, to every user
npm view some-lib dependencies # its transitive tree — each one is more of the same
# then ask: is the feature worth THAT, or can I write the 10 lines?

Sometimes the ten lines are the cheaper loan

A lot of tiny dependencies exist to save code you could write and own in minutes — and owning it removes all the interest. A debounce is the canonical example: a one-line reason to reach for a utility library, and a five-line reason not to:

// this is the whole "dependency" — writing it costs less than auditing a package for it
function debounce(fn, wait) {
  let t;
  return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); };
}

The rule is not “never depend” — it is “don’t borrow what is cheaper to own.” For debounce, deepClone of simple data, or a small formatter, the code you write has no bytes-tax, no CVE, and no upgrade day.

Borrow deliberately, and put a budget on it

For the genuinely hard, well-solved problems — dates, virtualization, a rich editor, crypto — do borrow, because reimplementing them badly is far more expensive than the interest. The discipline is to make it a decision: prefer a dependency that is small, focused, actively maintained, and tree-shakeable over a kitchen-sink library where you use 5%. Then enforce the aggregate with a bundle budget that fails the build when the total crosses a line, so “one more package” cannot quietly balloon the app one careless install at a time:

$ node harness/bundle-budget.js
  FAIL  entry chunk 268kb > 250kb budget  (moment 19kb, lodash 24kb — both replaceable)

That number turns “every dependency is a loan” from a proverb into a check. Borrow on purpose, own what is cheap to own, and keep a budget on the total — the harness-bundle-budget exercise builds exactly that gate, and debounce-utility and deep-clone are the “own it instead” cases in miniature.