← Writing

Trade-offs & Constraints

Every dependency is a bet

Choosing a library or framework is not a purely technical decision. It is a long-term commitment with consequences that compound over time.

4 min

Every dependency you add to a project is a bet.

You are betting that the library will be maintained. That the API will remain stable. That the community will survive a change of ownership or a shift in fashion. That security vulnerabilities will be patched. That someone on your team will still understand it in three years.

Most of the time, the bet pays off. But the terms are rarely read carefully.

The hidden contract

When you add a dependency, you are not just adding functionality. You are adopting a contract.

The contract says: we will use this library’s abstractions as the foundation for part of our system. We will update it when it changes. We will debug problems that originate inside it. We will monitor it for vulnerabilities. We will migrate away from it if it becomes untenable.

This contract is not written anywhere. It does not appear in the pull request description. But it is real, and it accumulates. A codebase with fifty dependencies has fifty active contracts. Some of those contracts will eventually become expensive.

The compounding problem

The cost of a dependency compounds in ways that are easy to underestimate.

A dependency added in year one is still there in year four. By then it may have gone through three major versions. The engineer who introduced it may have left. The original justification may no longer be true — or may no longer be remembered. The team now maintains something they did not choose and do not fully understand.

Worse, dependencies depend on dependencies. The npm ecosystem made this failure mode widely visible: a simple project can pull in hundreds of transitive dependencies, most of which no one on the team has ever read. Each one is a latent risk.

The question is not just “is this library good?” The question is “are we prepared to own everything this library brings with it?”

The build vs. own trap

There is a common failure mode in the opposite direction: building from scratch things that should have been a dependency.

Authentication, encryption, date parsing, email delivery — these are areas with well-understood requirements and well-tested solutions. Building them yourself rarely produces a better result. It produces a bespoke implementation that is less tested, less reviewed, and harder to update.

The decision is not “dependency or no dependency.” The decision is about which kind of risk is more acceptable: the risk of external change, or the risk of internal underinvestment.

Core domain logic should usually be owned entirely. Infrastructure and utility code usually should not.

A more deliberate process

Before adding a dependency, a few questions are worth asking.

Is this library actively maintained? Not just recently updated, but genuinely maintained — issues addressed, vulnerabilities patched, a roadmap that makes sense for where your system is going.

What is the abstraction cost? A dependency that leaks its internals throughout the codebase is far more expensive to replace than one that sits behind a clear interface. If you cannot draw a boundary around it today, you will not be able to remove it tomorrow.

What is the migration path if this goes wrong? If the library is abandoned or introduces a breaking change, how hard is it to move away? If you cannot answer that question confidently, the dependency carries more risk than it appears.

Could a smaller, more focused library do the same job? Large frameworks often bring capabilities you will never use and constraints you will eventually resent.

Dependencies as architectural decisions

The dependency graph of a project is an architectural document. It describes which external systems the project trusts, which abstractions it has adopted, and which communities its maintenance is coupled to.

Most teams do not treat it that way. Dependencies are added when needed and forgotten when they stop being new. The graph grows in one direction only.

A more deliberate approach treats each dependency as a choice with a known cost and a conscious owner. Not every dependency can be perfectly evaluated — the world moves fast and deadlines are real. But the posture of treating dependencies as bets, rather than as free functionality, produces better systems over time.

Every bet eventually settles. The question is whether you understood the odds when you placed it.