/ 6 min read

Fewer dependencies, stronger systems

How every added dependency increases long-term operational cost.

There is a seduction in modern software development. For every problem, there is a package. For every friction point, a plugin. We are tempted by the promise of immediate velocity: “Just install this, and the problem is solved.”

In the short term, the promise holds true. The feature ships. The ticket moves to “Done.” We feel productive.

But software is not a static artifact; it is a living system. When we evaluate the cost of a feature, we often only calculate the time to build it. We neglect the far greater cost: the time to keep it running. Primary software maintenance is not about fixing bugs in your own logic—it is about managing the friction of your dependencies.

The more external code you import, the less you own your system.

Dependency is debt

Every dependency—whether a small utility library, a UI framework, or a third-party service—is a contract. You are trading control for convenience. You are accepting a permanent maintenance rent in exchange for a temporary speed boost.

Consider the lifecycle of a meaningful dependency.

  • Day 1: It solves the problem perfectly. The team is happy.
  • Year 1: The library releases a breaking change (v2.0). You must pause feature work to refactor your code to upgrade, or choose to stay on an unsupported version.
  • Year 3: The maintainer loses interest or the community moves to a “newer, better” tool. Issues pile up. Security vulnerabilities appear. You now own this code, whether you understand it or not.
  • Year 5: The library conflicts with another core system you need to upgrade. You are stuck. The “time saved” five years ago has now been paid back with interest.

This is the hidden operational cost of complexity. A lean system with custom, foundational code might take 20% longer to build initially. But a heavy system built on a tower of abstractions will take 200% more effort to keep alive over a decade.

We often assume that “standard” libraries are safer than custom code. But in the long run, the only code you can truly rely on is the code you can read, debug, and fix yourself.

The illusion of modularity

We tell ourselves that libraries make our code more modular. In practice, they often introduce “glue code”—the messy, brittle logic required to make two opinionated systems talk to each other.

When you rely heavily on dependencies, your team stops solving the business problem and starts solving the “integration problem.” You spend your days fighting configuration files, version mismatches, and obscure error messages buried deep in a node_modules folder.

Furthermore, dependencies increase the cognitive load for every new engineer. Instead of learning the language and your domain, they must learn the language, your domain, and the specific API idiosyncrasies of a dozen third-party tools.

Principles for a stronger system

This is not an argument for “Not Invented Here.” We should not write our own cryptography, databases, or low-level network protocols. However, the default engineering stance should be skepticism, not adoption.

Here are a few principles to guide that decision:

1. Prefer the platform

Browsers and operating systems are powerful. CSS, JavaScript, and standard Linux utilities have evolved effectively. Before reaching for a heavy library to handle dates, animations, or forms, ask: “Can the browser do this natively?”

Often, the standard API is more stable, more performant, and guaranteed to exist in ten years. The library is not. The platform is the ultimate dependency; lean on it.

2. Own the critical path

If a piece of logic is core to your unique value, you should probably write it yourself. Outsourcing your core domain to a generic library means you are constrained by someone else’s design decisions. When your needs diverge from their roadmap, you hit a hard wall.

If your product is a dashboard, do not delegate the charting logic to a high-level abstraction that hides the rendering loop. Own the thing that makes you valuable.

3. Evaluate the exit strategy

Before running npm install or pip install, ask: “If I had to remove this in two years, how hard would it be?”

If a dependency creates a pervasive spiderweb throughout your codebase, it is a high-risk marriage. If it sits cleanly behind an interface you control, it is a manageable tool. Isolate your dependencies so that you can replace them without rewriting your application.

4. Boring is resilient

New tools bloom and fade. The “modern stack” of 2020 is already looking dated today. SQL, HTML, and HTTP are boring. They are also timeless.

Building on boring technology is the best way to ensure your system survives the hype cycle. “Boring” means documentation exists. “Boring” means stability. “Boring” means you sleep at night.

The long game

We need to shift our thinking from “how fast can we build this?” to “how long can we maintain this?”

Complexity is the silent killer of digital projects. It creeps in, one package at a time, until the system becomes too expensive to change and too fragile to touch. The strongest systems are often the ones with the fewest moving parts. They are easier to secure, easier to optimize, and easier to hand off to the next generation of engineers.

Every time we add a dependency, we are inviting a stranger into our home. It implies trust. It implies a long-term relationship. We should be much slower to sign those contracts.

In a world obsessed with new tools, the most advanced engineering decision is often the choice to use fewer of them.