Dependency
A dependency is anything one part of a system needs from another to do its work, and the word is what lets a team reason about what they have signed up to maintain when they choose to rely on it.
What It Is
A dependency is anything a component needs from outside itself to function. A Python module imports a JSON parser; a web service calls a database; a frontend hits an authentication API; an AI agent reaches for a search_code tool. In every case the dependent part doesn’t carry its own copy of what it needs: it borrows.
That borrowing is what the word names. A dependency is not just “something the code touches.” It is a promise the borrower has implicitly accepted: that the borrowed thing will continue to exist, behave the way it does now, and be reachable when needed. The depth of that promise is what distinguishes a casual use from a load-bearing dependency.
Dependencies sort by source as well as by depth:
- Library dependencies. Code you pull in at build time — an npm package, a Python wheel, a Rust crate, a vendored header. The dependency lives in your repository or your lockfile and ships alongside your binary.
- Service dependencies. A running system you call across a network — a database, a queue, an identity provider, an external API. The dependency is somebody else’s process, and your code’s behavior is partly that process’s behavior.
- Framework dependencies. A scaffold that calls your code more than your code calls it — a web framework, a test runner, an actor system. Framework dependencies shape the dependent code; you don’t get to ignore them by hiding them behind an interface.
- Data dependencies. A schema, file format, message shape, or model output the code assumes. Schema-shaped dependencies are easy to miss because there’s no
importline to grep for. - Tool dependencies. Anything the runtime expects to find —
git, a shell, a sidecar binary, a particular Python version, a GPU driver. Tool dependencies are usually invisible until the deploy lands somewhere they aren’t. - Agent tool dependencies. In an agentic workflow, the catalog of tools an agent can call is a dependency the same way library imports are. If a tool’s output format shifts or it disappears from the catalog, the agent’s plan breaks at the same level a library upgrade breaks code.
Two further distinctions are worth keeping in vocabulary. Direct dependencies are the ones a component declares for itself: the entries in its package.json or its import block. Transitive dependencies are what your direct dependencies depend on, recursively, all the way down. A project that lists six direct dependencies can easily have four hundred transitive ones, and most of them are invisible until one breaks. The second distinction is between declared dependencies and undeclared dependencies: the ones the lockfile names and the ones your code only happens to need because somebody else’s code initialized a global first. Undeclared dependencies are where surprises live, because nothing in the project’s own files reveals them.
In agentic coding the same vocabulary applies, and the unit of the dependency moves up a level. A coding agent depends not only on the libraries the codebase imports but on the model behind it, the system prompt that shaped it, the context it was given, the tools registered for the session, and the file paths it learned from earlier turns. Each of those is a dependency the agent reaches for; each can change underneath it; and most are undeclared in the sense that no part of the codebase records the agent’s reliance on them.
Why It Matters
Software gets built by standing on other people’s work, and the cost of doing that isn’t proportional to how much code you wrote. It’s proportional to how much you depend on. A team without the word “dependency” describes incidents as one-offs: “the build broke because the date library changed,” “the agent stopped finding files because the tool was renamed.” A team fluent in dependency talks about the same incidents as one phenomenon (we’re being moved by something we don’t control) and gets to design responses around the phenomenon instead of each instance of it.
Naming the class is what makes the response designable. Once a team can count dependencies, they can decide which ones are worth taking on, which to wrap, which to pin, and which to retire. Without the vocabulary, every dependency decision is made implicitly, one npm install at a time, with no running ledger of what the project has committed to.
There’s a second-order effect on how systems evolve. Every dependency is a bet that the depended-upon thing will keep working, keep being maintained, and keep being compatible. Some bets pay out for decades; some go bad in a quarter. Teams that don’t track the bets discover the bad ones during incidents, which is the worst possible time. Teams that do track them get to retire dependencies on their own schedule rather than the supply chain’s.
For agentic workflows the diagnostic urgency is sharper. Agents introduce dependencies faster than humans do. A single agentic session can add three libraries, wire two new services, and start calling a fourth tool, none of which the project would have taken on if a person had been asked to defend each addition. The rate of dependency growth scales with agent usage unless someone (a human, a policy, a check) names “is this a dependency we want?” as a decision worth pausing on. The word is what makes the pause possible.
How to Recognize It
You’re looking at a dependency whenever a change to something outside your control could change your behavior. A few concrete signs:
- The build broke and you didn’t change anything. A pinned version drifted, a transitive dependency released a bad patch, a registry went down. The change is somewhere up your dependency tree, not in your diff.
- The deploy works on one machine and not another. An unstated tool dependency — a binary on
PATH, an environment variable, a Python version — exists on one host and not the other. Nothing in the repository declares it, but something in the code needs it. - Removing something is a multi-week project. A library you imported in a hurry years ago now has consumers in fifty files. Replacing it requires changing all fifty. The cost of removal is the strongest signal that the dependency went deep.
- An agent rewrites code without flagging that it introduced a new package. The diff is small, the change reads sensible, and somewhere in the middle is
import some_new_library. The dependency joined the project by side door. - The status page of a service you “barely use” turns red and your product breaks. A dependency you described as auxiliary turns out to have been load-bearing. The architecture diagram disagreed with the runtime reality.
- A schema change in someone else’s database costs your team a sprint. The other team’s database is, for your purposes, a public API of an undocumented shape. Your code depended on that shape without anyone having written it down.
A few dependency-prone patterns are worth recognizing in their own right:
- Implicit version ranges. A lockfile pinned at
^4.2.0is not pinned; the next minor release of the library is part of the dependency. Pinning loosely is a way of taking on a dependency on the maintainer’s release discipline as well as on the code itself. - Singleton services. A platform team that says “everyone uses our auth library” has created a dependency that’s organizationally hard to remove. Coupling and dependency reinforce each other when one team’s product is everyone else’s runtime.
- Convenience clients. Vendor-provided SDKs that wrap an HTTP API often add their own dependency graph behind the convenience — telemetry, retry logic, automatic schema upgrades. The SDK is one declared dependency and twenty undeclared ones.
- Configuration-as-dependency. Environment variables, feature flags, and remote configuration services that change behavior without a code change. The code depends on those values; the dependency is real even if it doesn’t appear in
package.json. - Agent system prompts. When the same prompt is reused across teams or sessions, it becomes a dependency in the sense that changes to it propagate through everyone who relies on it. Treating the prompt as code (versioned, reviewed) makes the dependency explicit.
The honest test for a dependency is: if this thing changed or disappeared tomorrow, what would I have to do? If the answer is “nothing,” it isn’t a dependency you need to think about. If the answer is “rewrite three files” or “find a replacement,” it is one you owe a maintenance plan to.
How It Plays Out
A Node.js project installs a popular date library in 2018 and imports it directly in dozens of files. By 2025 the library is abandoned, a CVE is filed against an older version, and the team is forced to migrate. Because every consumer reached into the library directly, the migration touches the entire codebase and stretches across two quarters. After the dust settles, the team writes a DateService interface, hides the replacement library behind it, and the next migration becomes a one-file change. The vocabulary shift that locked the lesson in wasn’t “use interfaces.” It was learning to name moment as a dependency the project owed a maintenance plan to, not as part of the codebase.
A platform team announces that its authentication service is moving to a new identity provider. The migration was budgeted as a sprint; it takes a quarter. The reason isn’t the migration. The old service had thirty-odd consumers, and each consumer had grown its own handling of an undocumented field in the response payload. The team had been depending on the field — and on the response shape, and on the silent retry behavior — without ever declaring those dependencies. The fix isn’t the new provider; it’s a client library with a typed contract that makes the dependency surface explicit, so the next migration can plan around it.
An AI agent is asked to add a webhook handler. It writes the handler, runs the tests, and commits. Two days later the build breaks in CI for an unrelated reason, and the team discovers that the agent’s solution pulled in two new npm packages with seventy transitive dependencies between them — none of which existed in the repository the morning the agent started. The change was a hundred lines of code and a hundred thousand lines of dependency graph. The team’s policy after the incident is straightforward: agents must declare any new dependency they introduce, name what the dependency provides, and explain what the project would lose without it. The number of npm installs drops sharply over the next month.
“Before adding any new package or service, check whether something already in the dependency graph can do the job. If you need to add a new dependency, write one sentence at the top of the PR description naming the package and what we get from it that we couldn’t get otherwise.”
Consequences
When a team has the vocabulary of dependency and uses it deliberately, the project’s supply chain stops surprising them. Additions are decisions, not accidents. Removals are planned, not emergencies. The team can answer “what would break if we lost service X tomorrow?” without having to grep the whole repository to find out.
The honest tradeoffs are worth naming, because tracking dependencies isn’t free.
- Vigilance has costs. Every dependency tracked is something somebody has to watch — for releases, for CVEs, for deprecation notices, for license changes. A team that takes the discipline seriously will spend hours per week on dependency hygiene that they didn’t spend before, and those hours come out of feature work.
- Wrappers can ossify. Hiding a dependency behind an interface buys swappability at the cost of one more layer for a reader to traverse. Over-wrapped systems are hard to follow because the path from “code that runs” to “library that does the work” passes through three thin adapters that each add nothing.
- Zero-dependency thinking is its own trap. A project that refuses to take on dependencies ends up reinventing JSON parsing, HTTP clients, date arithmetic, and a hundred other solved problems, badly. The point of the vocabulary is to make dependency decisions deliberate, not to drive the count to zero.
- Some dependencies are intrinsic and shouldn’t be hidden. A consumer that genuinely needs to act on the rate limits, error semantics, or eventual-consistency window of a service it depends on can’t be insulated from those by an interface that pretends they don’t exist. The vocabulary helps you tell necessary dependency from accidental dependency; both exist, and treating them the same is its own mistake.
- The undeclared-dependency tail keeps growing. Even a disciplined project accumulates undeclared dependencies — implicit assumptions about the runtime, the deploy target, the agent’s tool catalog. Naming dependency as a concept doesn’t surface these automatically; what it does is give the team a place to file them when they show up.
The goal isn’t zero dependency; the goal is dependency that’s named, declared, wrapped where it deserves to be, and retired before the supply chain forces the team’s hand.
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” framing 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 dependency injection and the alternative service locator approach, and his “separating service configuration from the use of services” framing is the conceptual ancestor of the isolate-and-wrap practice described here.
- Eric Evans introduced the Repository pattern as 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 wrap-the-database example used in this article’s recognition 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 “pinning is a discipline” point a shared grammar across ecosystems. Pinning works as a practice only because there’s 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 transitive-dependency framing in the Recognition section names this folk concept directly.