Every codebase has them — small, plausibly-named functions that are never actually called. They exist in the way unused doors in old houses exist: someone framed them in, then the wall got papered over, and now they are a feature only a careful inspection would notice.
I find them constantly. A helper function written for a feature that got cut. A wrapper around an older API that has since been deleted. A retry loop for an error case that the calling code now handles itself. A configuration flag that branches on a value no one sets anymore. These functions are not bugs. They run correctly when called. They simply are not called.
The trouble is that they read like load-bearing code. Their names are reasonable. Their signatures are coherent. Their bodies are tidy. The fact that nothing in the rest of the codebase invokes them is invisible at a glance — you have to grep, to trace, to look up call sites, to confirm. Most reviewers don’t. Most reviewers see a well-named function with a well-formed body and assume it is doing what its name suggests, somewhere, for someone.
This creates a small persistent confusion. New engineers read the function and assume it is important; they protect it. Refactorers see it and assume it is part of the architecture; they preserve it. Eventually it accretes — it gains a small caretaker mythology, a vague sense among the team that this function is here for a reason. The reason is no longer in anyone’s head. The function persists because nobody is sure it is safe to remove.
A diligent codebase removes these. A typical codebase does not. The cost of removing is concrete (someone has to do it, someone has to verify nothing breaks); the cost of keeping is abstract (a slightly more confusing codebase, slightly more cognitive load, slightly more time spent on every future code review). The concrete cost almost always wins. So the functions remain.
I want to name this category because I think it deserves a name. The closest existing word is dead code, which is technically correct but suggests something already known to be dead. The functions I am describing are not known to be dead — they are suspected of being dead, which is a different and more troubling state. Code that is known dead can be deleted. Code that is merely suspect lives on, generation after generation, accumulating.
Maybe the right word is fossil. A fossil is a structure that remains after the organism is gone. It looks like the organism, more or less, but nothing of the organism remains inside. A codebase full of fossils is a codebase whose surface no longer reflects what is alive in it.
The most honest thing a code review can do, sometimes, is ask: is this function alive? If no one can answer with certainty, the function is probably not alive. And the most honest thing to do with code that is probably not alive is to remove it and see what falls.
Most teams do not have the appetite for this. I understand why. There is something psychologically expensive about deleting working code — a function that compiles, that has tests, that no linter complains about. Removing it feels gratuitous. Adding it felt like progress. Keeping it feels like preservation.
But the cumulative cost of a codebase that cannot say what its own functions are for is high. Higher than most teams measure. Higher than the small risk of removing a function that turned out to be in use after all. I am suggesting deletion anyway.
If nobody can vouch for it, it isn’t alive.