A Dependency Is a Standing Obligation
The decision to add a dependency almost always gets evaluated at a single point in time: we need this capability, this library provides it, writing it ourselves would take two weeks, installing it takes two minutes. Framed that way it’s obviously correct, and often it genuinely is — the accumulated work in a mature library is real and reinventing it is usually foolish. But the framing hides the shape of the cost. You aren’t buying a thing; you’re entering into an ongoing relationship, and the install is the cheapest moment you will ever have with it.
What follows the install is a stream of small obligations. The library releases a version with a security fix, so you upgrade — and the upgrade includes an unrelated breaking change, so now you’re editing call sites for a fix you didn’t ask for. It picks up its own new dependencies, which you now transitively own. It changes its default behavior in a minor release and something subtly shifts in production. None of these are disasters. They’re a tax, levied at unpredictable intervals, on a decision that was priced as though it were one-time. Multiply by the number of dependencies in a mature project and a nontrivial share of your maintenance capacity is spent servicing choices made years ago in two minutes each.
The sharper risk is what happens when the relationship ends from the other side. The maintainer moves on, the company sunsets the product, the project gets archived with a note thanking everyone. Nothing breaks that day — this is the quiet failure mode. What breaks is later: a security advisory arrives with no patch coming, or a platform upgrade needs a version that will never be released. Now you’re maintaining a fork of something you never understood, or doing the two-week build you avoided, except under time pressure and with a working system depending on the result. The bill for the afternoon you saved comes due at the worst possible moment.
None of this argues for building everything yourself; that trades a known tax for a much larger one and you’ll do a worse job than the mature library. It argues for pricing the decision honestly, which mostly means asking different questions at install time. How much of this library do I actually use — is it a hundred lines of functionality behind a large surface? How healthy is it: recent releases, more than one maintainer, a real user base? How hard would replacing it be — is it behind an interface I control, or will its types spread through my codebase until it can never leave? A dependency that’s easy to remove is a much smaller commitment than one that isn’t, and that’s mostly determined by how you integrate it, not by what it is.
The habit worth building is to feel the weight of the obligation at the moment you take it on, when it’s cheapest to decline. Some dependencies are absolutely worth it — the cryptography library, the database driver, the things that are genuinely hard and where being wrong is catastrophic. Others are a trivial utility that costs less to write than to keep. The difference isn’t how useful the thing is on the day you add it; it’s the ratio between what it does for you and what you’ll owe it for the rest of the system’s life. Most teams never do that arithmetic, because the install is easy and the invoices arrive one small piece at a time, years later, addressed to someone else.