Dependency
Context
No component exists in a vacuum. To do its work, it relies on other pieces: libraries, services, frameworks, data sources, or tools. A dependency is anything a component needs to function. The concept operates at the architectural scale and is central to understanding both the structure and the fragility of a system.
Dependencies come in many forms: a Python package imported from PyPI, a database a service connects to, an API a frontend calls, or a tool an AI agent is given access to. Some dependencies are chosen; others are inherited.
Problem
How do you rely on things you don’t control without becoming hostage to them?
Forces
- Using existing libraries and services saves enormous effort. No one should rewrite a JSON parser.
- Every dependency is a bet that the depended-upon thing will continue to work, be maintained, and remain compatible.
- Transitive dependencies (dependencies of your dependencies) multiply risk invisibly.
- Removing or replacing a dependency after the fact can be expensive, especially if your code is tightly coupled to it.
Solution
Treat dependencies as conscious decisions, not accidents. For each dependency, ask: what does this give us? What does it cost? What happens if it disappears or changes?
Practical strategies for managing dependencies:
- Minimize. Don’t depend on things you don’t need. A dependency that saves ten lines of code but adds a maintenance burden isn’t worth it.
- Isolate. Wrap external dependencies behind your own interfaces. If you access a database through a
Repositoryinterface, swapping databases is a local change. - Pin. Specify exact versions so that updates are deliberate, not surprises.
- Audit. Periodically review your dependency tree for abandoned, vulnerable, or bloated packages.
In agentic workflows, the tools you give an AI agent are its dependencies. If an agent depends on a deploy tool that silently changes its behavior, the agent’s workflow breaks, just as a library upgrade with breaking changes breaks your build. Treat agent tool definitions with the same care you give code dependencies.
How It Plays Out
A Node.js project installs a popular date library. A year later, the library is abandoned and a security vulnerability is discovered. Because the team imported the library directly in dozens of files, replacing it touches the entire codebase. A team that had wrapped it behind a DateService interface would only need to change the wrapper.
An AI agent relies on a search_code tool to work with a repository. When the tool’s output format changes (line numbers are no longer included), the agent’s parsing logic breaks. The developer who maintains the agent’s configuration updates the tool description and adjusts the prompt, treating the tool dependency the same way they’d treat a library upgrade.
The node_modules folder — or its equivalent in any ecosystem — is a dependency graph made visible. Glancing at its size can be a useful gut check: if your project has 400 transitive dependencies, you are standing on a tower of other people’s decisions.
“We use the moment library in dozens of files. Wrap it behind a DateService interface so that if we need to replace it later, we only change the wrapper.”
Consequences
Well-managed dependencies let you benefit from the broader ecosystem without being trapped by it. Isolation through interfaces makes dependencies swappable. Version pinning makes updates predictable.
The cost is vigilance. Dependencies require ongoing maintenance: updates, security patches, compatibility checks. Ignoring them creates a growing liability. But obsessing over “zero dependencies” leads to reinventing well-solved problems. The balance is having the dependencies you need, wrapped behind stable interfaces, with a clear plan for maintaining them.
Related Patterns
Sources
- David Parnas framed dependencies as a design concern in “On the Criteria To Be Used in Decomposing Systems into Modules” (Communications of the ACM, 1972), arguing that a module should hide the design decisions it depends on so that change does not ripple through the system. The “wrap dependencies behind your own interfaces” advice in this article is a direct application of his information-hiding principle.
- Martin Fowler’s “Inversion of Control Containers and the Dependency Injection pattern” (martinfowler.com, 2004) is the canonical modern treatment of how to keep code from being hostage to the things it depends on. Fowler named the dependency injection style and the alternative service locator approach, and his framing of “separating service configuration from the use of services” is the conceptual ancestor of the isolate-and-wrap practice described here.
- Eric Evans introduced the Repository pattern as a way to put a stable, domain-shaped interface in front of a volatile persistence dependency in Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley, 2003). The
Repositoryexample used in the Solution section is his. - Tom Preston-Werner authored the Semantic Versioning specification (semver.org, first published 2011, current version 2.0.0 from 2013), which gives the “pin exact versions” advice a shared grammar across ecosystems. Pinning works as a discipline only because there is a public convention for what version numbers mean.
- The colloquial term “dependency hell” emerged from the Unix and Linux package-management communities in the early 2000s, building on the earlier Windows-specific “DLL hell” of the 1990s. The Forces section’s “transitive dependencies multiply risk invisibly” framing names this folk concept directly.