A dependency is external code that your project relies on to function, like a library or package you install rather than writing yourself. If your app uses a tool someone else built to handle dates, send emails, or draw charts, that tool is a dependency: your code depends on it being present and working. Almost every real project has them, often dozens, because reusing trusted code is faster and safer than rebuilding everything from scratch. The trade-off is that each dependency is something you must keep updated, trust, and occasionally untangle, so understanding how they work is part of writing maintainable software.
How dependencies work
You rarely copy a library file by hand. Instead, you declare which dependencies your project needs in a manifest file, and a package manager fetches and installs them. In a JavaScript project that file is package.json and the manager is npm; in Python it is often a requirements file with pip; other ecosystems have their own equivalents.
// a package.json snippet declaring two dependencies
{
"dependencies": {
"express": "^4.19.0",
"date-fns": "^3.6.0"
}
}
When you run the install command, the package manager reads that list, downloads the right versions, and places them where your code can import them. This is why a fresh checkout of a project needs an install step before it will run, and it ties directly into what a codebase is: the dependencies are declared in the codebase but usually not committed line by line.
Direct vs transitive dependencies
Not every dependency is one you chose.
| Type |
What it is |
| Direct dependency |
A library you explicitly added and use |
| Transitive dependency |
A library your dependencies depend on, pulled in automatically |
You might add one charting library directly, and it might quietly bring in a dozen smaller packages it needs. Those are transitive dependencies. They matter because a problem in one of them, a bug or a security flaw, affects you even though you never chose it. This is part of why dependency trees can grow surprisingly large.
Versions and lockfiles
Dependencies change over time, and an update can fix bugs or, occasionally, break your code. Version numbers and lockfiles manage this.
A manifest usually specifies an acceptable version range, like accept any 4.x release. A lockfile then records the exact versions actually installed, so that everyone on the team and every server builds with the identical set. That reproducibility is the whole point: it prevents the it-works-on-my-machine problem caused by two people having slightly different versions. Commit your lockfile.
When to add a dependency, and when not to
A dependency is a convenience and a liability at once. Good ones save you from reinventing hard, well-solved problems like cryptography or date math, where a mistake would be costly. But each one is code you must trust, update, and audit, and it can break or be abandoned. Before adding one, ask whether a few lines of your own would do, how widely used and maintained the library is, and what it drags in transitively. Reaching for a heavy package to avoid writing ten lines is a habit worth breaking. For the wider workflow, how to use Git and GitHub covers how teams track these manifest and lockfile changes safely.
What to skip
- Adding a library for a trivial task. If a few lines do it, write the few lines.
- Ignoring the lockfile. Commit it; without it, builds drift and break unpredictably.
- Never updating. Stale dependencies accumulate bugs and security holes; update on a regular cadence.
- Trusting blindly. Check that a package is maintained and widely used before depending on it.
FAQ
What is a dependency in simple terms?
External code your project needs to work, like a library you install instead of writing that functionality yourself.
What is a package manager?
A tool, such as npm or pip, that installs, versions, and updates your dependencies based on a manifest file you declare them in.
What is the difference between a direct and a transitive dependency?
A direct dependency is one you added on purpose. A transitive dependency is one your dependencies need, pulled in automatically.
Why should I commit my lockfile?
The lockfile pins exact versions so every machine builds with the same set, which prevents subtle bugs from version drift.
Where to go next
What is a codebase, What is a library, and How to use Git and GitHub.