
Garlic in the Codebase: Identifying and Removing Toxic Dependencies
There’s a certain smell that fills the room when something doesn’t belong in your codebase. It’s faint at first—subtle, even masked by the usual aroma of late-night coffee and burned-out CPUs. But then it hits: acrid, stubborn, and offensive to your engineering sensibilities. I call it garlic. You might call it a toxic dependency.
I’ve been working in software long enough (several centuries, give or take) to see my fair share of corrupted systems and cursed libraries. Whether you’re a mortal developer or an undead architect like myself, you’ll eventually encounter code that smells off. Not because it’s unfamiliar, but because it’s incompatible with life—your project’s life, that is.
So what exactly is a toxic dependency? In my experience, it’s any package, plugin, service, or system that compromises the health of your codebase. Sometimes it’s poorly maintained, other times it’s overly complex or misaligned with your needs. Most dangerously, it might be deeply embedded—resistant to removal, silently poisoning everything it touches.
The Subtle Signs of Contamination
Toxic dependencies rarely announce themselves outright. They prefer the shadows. At first, they may even appear helpful. A nifty library that saves time, a shiny new framework all the other devs are using, or an external service that promises to “handle the hard stuff.” We let them in willingly. We let them spread.
The first warning sign is friction. Things that used to be simple become convoluted. Small changes require large refactors. Adding new features means navigating undocumented behaviors, obscure errors, or bizarre performance issues. And no one wants to touch the part of the code that deals with it—not because it’s sacred, but because it’s cursed.
I once worked on a project that integrated a middleware solution so bloated, so opaque, and so outdated, it was easier to write around it than through it. That solution had dependencies with their own dependencies, some of which hadn’t been updated since the late 1700s. (Okay, early 2000s, but it felt like the Enlightenment era.) We couldn’t update Node without breaking it. We couldn’t remove it without a full rewrite. It just sat there, hissing in the dark.
Dependency or Possession?
A dependency isn’t inherently evil—let me make that clear. In fact, healthy dependencies are the foundation of modern development. We rely on ecosystems of open-source libraries, SDKs, and APIs to build faster and better.
The problem begins when your dependency takes over the project. When architectural decisions are made not based on what’s best, but on what the dependency allows. When your code begins to shape itself around someone else’s patterns, limitations, and bugs. At that point, it’s no longer a relationship. It’s possession.
I’ve seen entire teams become reluctant to change anything because “that’s how the package does it.” The original purpose of modularity and reuse is replaced by dogma. “We can’t switch to something else,” they whisper. “It’s too late. We’ve bound ourselves.” They say this while pulling in polyfills for IE9 to support a feature no one uses. They say it while staring into the middle distance, remembering a time when things made sense.
The Legacy of Garlic
Toxic dependencies become legacy fast. Sometimes immediately. And they tend to rot in place. You know the ones: that helper file from a random repo, that jQuery plugin from 2011, that authentication module no one fully understands. Like real garlic to a vampire, these pieces are repellents to progress.
There’s shame involved, too. No one wants to admit that part of their project is beyond saving. “We’ll fix it later,” they say. “We just need to finish this feature first.” And so the garlic stays. It lingers. Future team members smell it but dare not name it. The garlic becomes tradition.
The longer a toxic dependency remains in your codebase, the more it influences everything around it. Suddenly, your test suite is brittle. Your deployments require rituals. Your bugs are difficult to isolate because they live in a fog of abstraction created by outdated or unmaintained code.
Driving a Stake Through It
Removing toxic dependencies isn’t easy. It requires courage, planning, and a willingness to say, “This is not fine.” You’ll need to document the areas of the code that interact with it, explore replacements, and make peace with the fact that removing one thing may require changing many others.
I’ve led purges of cursed code more than once. It starts with a spike—a proof of concept showing that life without the garlic is possible. Then come the rewrites, the adapter layers, the moment someone breaks production and everyone panics. That’s part of the ritual. You cannot extract poison without causing a little pain.
But once it’s gone—once the dependency has been ripped out root and all—the air clears. Tests run faster. Bugs stop manifesting like apparitions. Developers volunteer to work in parts of the codebase that were once considered haunted. You begin to build again, not just survive.
Keeping the Garlic Out
Of course, the best way to deal with toxic dependencies is not to invite them in to begin with. Before adding anything, ask yourself:
- Is it maintained?
- Is it modular?
- Does it align with your architecture?
- Will it still make sense in two years?
Treat new dependencies the way I treat strangers at a castle door after sunset: with skepticism, curiosity, and a healthy respect for ancient curses.
Look at your project like a living thing. What you feed it matters. Not everything tasty is nutritious. Not every shortcut is worth the bite later. And sometimes, the thing you thought was saving time is actually eroding your codebase from the inside.
Choose wisely. Clean often. And for the love of all that compiles, leave the garlic at the door.