Software Engineering

Every Dependency Is an Operational Commitment

Adding a library takes seconds and gets evaluated on features. The real cost arrives later, in upgrades, breakages, and the maintenance burden nobody scoped when the decision was made.

Why choosing a dependency is a long-term operational decision rather than a shortcut, and how teams end up maintaining software they never intended to own.

Jay McBride

Jay McBride

Software Engineer

4 min read

Introduction

Installing a package is the cheapest decision in software and one of the longest-lived.

It takes one command, gets justified in one sentence in a pull request, and then stays in the system for years — through upgrades, security advisories, breaking changes, and eventually a maintainer who stops responding.

The evaluation almost always happens on features. Does it do the thing? It does. Merged.

This article is for teams whose dependency list has grown faster than their capacity to maintain it. That gap is not visible until something in it breaks.

The Core Judgment: You Are Not Choosing a Feature, You Are Adopting a Maintenance Relationship

The question is not whether a library solves today’s problem. Most candidates do, or you would not be looking at them.

The question is what you have agreed to do for the next several years.

You have agreed to upgrade it when a CVE lands. To adapt when its API changes. To debug it when its behavior surprises you at three in the morning. To understand it well enough to work around it. And if it is abandoned, to fork it, replace it, or absorb its problems permanently.

None of that is in the README. All of it is in the commitment.

How This Breaks in the Real World

The most common failure is not a dramatic one. It is accumulation.

Each dependency is defensible in isolation. Together they produce an upgrade surface that exceeds what the team can keep up with. So upgrades get deferred, versions drift years behind, and the eventual forced upgrade — driven by a security advisory or a runtime end-of-life — becomes a multi-week project instead of a routine bump.

The second failure is depth. The library you chose has its own dependencies, which have theirs. You reviewed one package and inherited a hundred. When something breaks four levels down, you are debugging code you never chose and cannot easily replace.

The third failure is the abandoned project. It works fine, right up until a language or runtime upgrade it will never receive. Now the choice is fork it, replace it, or freeze part of your stack around it — and the third option is what many teams pick by default, without deciding to.

The fourth is subtler: dependencies leak into your design. A library’s opinions become your architecture. By the time you want to leave, the cost is not swapping a package. It is unwinding an assumption that spread through the codebase.

A Real Example: Two Days Saved, Three Weeks Spent

I worked on a project that pulled in a date-handling library to solve a formatting problem that would have taken maybe two days to write directly, narrowly, for the cases we actually had.

The library was excellent and did far more than we needed. Over the following two years its API surface spread through the codebase, because it was there and it was convenient.

Then a major version arrived with breaking changes, alongside a runtime upgrade we could not postpone. Migrating took roughly three weeks, most of it spent auditing call sites that had nothing to do with the original formatting problem.

The library was not the mistake. Reaching for it without asking how much of the codebase would end up touching it — that was the mistake.

What I Would Do Instead

I still use dependencies constantly. Writing your own crypto, date math, or HTTP stack is worse. But I evaluate differently now.

Before adding one, I ask:

  • how much of our code will end up importing this directly?
  • what is the realistic cost of replacing it in three years?
  • is it actively maintained, and by more than one person?
  • how deep is its own dependency tree?
  • if it disappeared tomorrow, what would we do?

I care much less about how popular it is and much more about how contained it is. A dependency touched in one module is a decision. A dependency touched in two hundred files is an architectural constraint.

For anything I expect to be long-lived, I wrap it — a thin internal interface over the library rather than direct imports everywhere. It costs a little up front and turns a rewrite into a swap.

And when the need is genuinely small and well understood, I write it. Not out of principle. Because forty lines I own are cheaper to maintain than a package I do not.

Closing

Dependencies feel free because the install is instant and the benefit is immediate.

The cost is real but deferred, arriving as upgrade work, security response, and the slow discovery that a library’s assumptions became your architecture while you were not paying attention.

Adding a package takes one command.

Living with it takes years.

Share

Pass it to someone who needs it

About the Author
Jay McBride

Jay McBride

Software engineer with 20 years building production systems and mentoring developers. I write about the tradeoffs nobody mentions, the decisions that break at scale, and what actually matters when you ship. If you've already seen the AI summaries, you're in the right place.

Based on 20 years building production systems and mentoring developers.

Support my work on Buy Me a Coffee
Keep Reading

More Articles

/ 3 min read

Feature Flags Solve Problems Until They Become the Problem

Flags are great for rollout safety. They are terrible as a long-term strategy for avoiding cleaner decisions.

Read article
/ 3 min read

Why So Many Internal Tools Become Permanent Accidents

Internal tools are supposed to be quick fixes. Then the business starts depending on them and nobody wants to admit a prototype became infrastructure.

Read article