How one npm install quietly turns into 80 packages you've never heard of.
You need to parse some JSON, format a date, or left-pad a string. The solution is obvious; there's a package for that. One npm install later and you're done. Clean and idiomatic. Some might say, the way modern software is written.
But what did that one command actually cost you?
When you add a dependency to your project, you're not just adding one package. You're adding that package and everything it needs to function. Those are called transitive dependencies; packages required by your dependency, and by its dependencies, resolved recursively. You didn't choose them. You may have never heard of them. But they're now part of your software.
The numbers are striking. A 2025 empirical study of npm projects found that the average project declares around 31 direct dependencies but ends up pulling in roughly 53 transitive dependencies; a 4.32x amplification rate, with some projects exceeding 10x 1. Another analysis places the average npm project at 79 transitive dependencies per project 2.
Maven (Java) is even worse: projects average just 5.4 direct dependencies but pull in 74.9 transitive dependencies, a 24.7x amplification and a total attack surface of over 80 packages 1.
At the enterprise scale, the picture gets stark. The Black Duck OSSRA 2025 report found that commercial codebases average 911 open source components, with 64% of them being transitive; roughly 583 packages per application that nobody explicitly chose 1.
You write import leftPad from 'left-pad'. You get a tree.
The cost of a dependency isn't just disk space; though that cost is real. A common experience: you clone a simple project, run npm install, and watch hundreds of megabytes fill your node_modules directory 1. Entire tools exist just to prune that directory back down 2.
But the deeper costs are the ones you can't see:
Security surface area. Every package you depend on is a potential vector. The most vulnerable npm packages in 2026 include widely-used libraries like lodash, cross-spawn, and gun.js, all carrying unpatched transitive dependencies that sit in dependency trees used by millions of applications 1. These aren't edge cases; they're in the trees your project pulls in without asking.
Fragility. On March 22, 2016, a developer named Azer Koçulu unpublished a package called left-pad; 11 lines of code that padded strings. Thousands of projects, including Babel and React, instantly broke and could no longer be built or installed 1. npm was forced to take the unprecedented step of restoring the package to prevent further damage 2. The internet didn't break because of a sophisticated attack. It broke because one person removed 11 lines of code that thousands of projects depended on without realizing it.
Cognitive load. If you can't understand what your software does; if its behavior depends on the internal logic of 80 packages you've never read; then you don't fully own your software. You're operating a system you can't reason about, can't debug at the edges, and can't fully trust.
Performance. This compounds at the application layer. Electron-based desktop apps bundle an entire Chromium browser, typically weighing in at 100-300 MB and consuming hundreds of megabytes of RAM; even for simple applications 11. By contrast, purpose-built native apps using the system's own webview can come in at 2.5-3 MB and use 30-40 MB of RAM 3. That's roughly a 100x difference in binary size for functionally equivalent software. The dependencies aren't just heavy individually; they stack into something that fundamentally changes the character of the application.
Let's be clear: the answer isn't to write everything from scratch in a vacuum. That's impractical, and it ignores the genuine value of shared, battle-tested code. Cryptography, protocol implementations, complex parsers; these are domains where using a well-maintained library is the correct decision.
The point is that each dependency should be a deliberate, justified choice; not a reflex. Not something you reach for because it's one npm install away. When you add a dependency, you should be able to answer:
When the answer is "the standard library can handle this" or "I could write this in an afternoon," the dependency isn't saving you time. It's exporting complexity into your future.
This is the philosophy behind cherries.works. We want to build tools that solve real problems without requiring an ecosystem of dependencies to function. Tools that start instantly. Tools you can understand; not just use, but understand, from top to bottom, because the code is small enough and focused enough to actually read.
Every dependency we avoid is a package we don't have to audit, a vulnerability surface we don't inherit, a startup cycle we don't have to pay for, and a piece of behavior we don't have to take on faith. That's not stubbornness. It's a design principle.
Software should respect the person reading its source code as much as the person running it. And the best way to respect both is to keep it lean.