Anti-Patterns: Dead Code & Lava Flows
In this article we’ll look at the definitions and problems of two software development anti-pattern cousins; Dead Code and Lava Flows. We’ll go through their dangers if left to grow, their causes, and If you bear with me until the end, I’ll provide solutions to both.
Since my last article was about the ice cream cone anti-pattern, it’s only fitting that now I explore the other end of the temperature spectrum, not to mention the theme fits Halloween rather nicely. But first, let’s review what an anti-pattern is:
“An anti-pattern is a common solution to a recurring problem that is ineffective or counterproductive. It is the opposite of a best practice and often leads to software that is difficult to understand, maintain, and extend.” — Teamhub Blog
There are many anti-patterns, and eventually, I hope to write about all of them. I’ll do my best to highlight them in the context of low-code (Mendix). The patterns themselves are often universal, regardless of language or platform, though some are more specific than others. Many anti-patterns are consequences of our ways of thinking-fallacies we fall into when making decisions about how we develop solutions.
Dead Code
Dead code consists of parts of your codebase that are either never called or do nothing useful. Essentially, it’s anything that contributes nothing. Examples of dead code include microflows that are never invoked, variables that are never used, or pieces of logic that sit unused. One could argue (and I do) that one-time-use scripts become dead code the moment they’ve been run. Dead code is bloat; it’s codebase litter-pieces of code that someone didn’t remove, either out of ignorance or fear.
Causes
Dead code is often created by accident and rarely intentional. It’s not always code that is unreachable but may simply never be invoked due to a condition before it (e.g., feature flag or decision split). Forgetfulness, disorganized workflows, and undisciplined practices are all prime suspects. Developers might knowingly create dead code due to tight deadlines, perhaps with the intention of coming back to clean it up later-but we all know how the tech debt story goes.
- Feature removal: When features are deprecated or removed, developers may leave related code in place.
- Refactoring: During code refactoring, some parts of the code may become unnecessary, but they might not be deleted immediately.
- Poor code management: Lack of proper code reviews and cleanup can lead to the accumulation of dead code over time.
- Hoarder syndrome: “ But what if we’ll need it in the future”
Examples:
- Excluded microflows/pages that are forgotten.
- Variables or parameters that have no use.
- One-time-use scripts that stay forever.
- Code tied to a feature flag that should be removed.
Problems
Just like litter or a dirty bedroom, the impacts of dead code aren’t readily apparent until it grows to an overwhelming size. Ultimately, it just gets in the way of everything, creating unnecessary time sinks, confusing developers, and contributing nothing positive.
- Increased maintenance costs: It makes the codebase more difficult to understand and maintain, as developers must sort through unnecessary parts.
- Bugs & confusion: Dead code might unintentionally interact with other parts of the system or cause confusion.
- Larger codebase: More code means higher complexity and longer compilation times, even if the dead code isn’t actually used.
Lava Flows
Much like its geological namesake, the Lava Flow anti-pattern represents code that’s built up over time, hardened in place, and impossible to move or refactor without significant risk. Lava Flow occurs when dead code, obsolete design elements, and legacy structures remain embedded in a system, even though they no longer serve a useful purpose. Often, these remnants were introduced during rapid development phases, experimental implementations, or early versions of the software that were never properly cleaned up.
Over time, these “fossilized” chunks of code become difficult to remove without potentially breaking the functionality of the overall system. Typically these pieces have no documentation, it’s generally unclear how they work or what it exactly does, but somehow the system works and therefore, just like a real lava flow everyone is afraid to touch it so instead of refactoring they they build on top of it.
Symptoms of Lava Flow
- Obsolete Code: Old methods, functions, or classes that are no longer used but have not been removed.
- Unclear Ownership: Parts of the codebase that no one on the current team fully understands or can explain. Usually someone wrote it long, long ago.
- Increased Complexity: Over time, as the codebase evolves, unnecessary complexity grows, making it harder to introduce new features or refactor existing ones.
- Fear of Change: Developers become hesitant to modify certain parts of the code due to the risk of breaking the system, leading to stagnation.
The Risks of Ignoring Lava Flow
Leaving these hardened chunks of code in place comes with significant risks:
- Reduced Agility : As more “lava” builds up, adding new features or refactoring becomes an uphill battle, slowing down the pace of development.
- Higher Maintenance Costs : Debugging and maintaining a bloated codebase is more time-consuming and error-prone, leading to higher costs over time.
- Technical Debt : Every time a developer works around a legacy block of code instead of addressing it, the technical debt grows, making future development even harder.
Solutions
CULTURE
If there’s one thing I hope you take away from these solutions, it’s this:
Being a real professional software developer means taking ownership not only of the code you write but also the code you inherit. When you see something broken or inefficient, it’s not ‘someone else’s problem’-it’s an opportunity to step up, fix it, and leave the codebase better than you found it. Responsibility in software isn’t limited to your own contributions; it extends to the health of the entire system.
Regardless of your role, experience, or title, you can inspire others by setting the example-doing the right thing yourself. If you consider yourself a professional, or aspire to be one, excellence must be your standard. That means not allowing bad code to go unfixed or uncleaned. To combat anti-patterns, fostering a culture of refactoring, optimization, and leaving code better than you found it is crucial. This is the hardest challenge because it starts with the individual, and often, people only take it up once they’re inspired by someone else. Maybe that someone is you, dear reader.
Instilling this kind of culture in your engineering department or you team is hard. Damn hard. The easiest remedy I can come up with is to lead by example. The benefits of maintaining clean code are not immediately rewarded, it takes learning to love what initially feels like a drag.
UNIT TESTING
To address the risk of refactoring and removing code, there’s really only one valuable answer: automated tests, namely unit tests. Nothing will give you more confidence in touching code you know nothing about. If the codebase you’ve jumped into has no tests, I feel for you-but let the practice start with you, for the developer who comes after.
Imagine working on a project where you could confidently change any piece of code, knowing the unit tests have your back-immediately alerting you if anything breaks. It may sound like a pipe dream, but it’s not as far off as it seems. The first step towards that level of confidence is to start building unit tests today.
OWNERSHIP
Ensuring that each part of the codebase has a clear owner is essential for maintaining its long-term health and quality. A designated owner takes responsibility for understanding, maintaining, and improving that section of the code. This ownership doesn’t mean exclusivity-other developers can contribute-but it ensures accountability. Code ownership empowers developers to take pride in their work while fostering a collaborative environment where accountability and shared responsibility keep the entire system healthy.
True code ownership isn’t just about maintaining and improving your code while you’re there; it’s about preparing it for the next developer who will take it over
Also, consider this: as a developer, it’s almost certain you’ll move on from your current project to another at some point. True code ownership isn’t just about maintaining and improving your code while you’re there; it’s about preparing it for the next developer who will take it over. Think of yourself as the caretaker of a “code child”-whether it’s a small component or a large system. Your responsibility is to nurture it, clean it up, and make it as resilient as possible, so it’s ready to thrive long after you’re gone. Write code today that future developers will thank you for.
DEBT TRACKING
There are realities we can’t avoid, no matter how idealistic we are. Sometimes, despite our protests, we simply aren’t given the time to do things the “right” way, or we lack the personal bandwidth or know-how to fix what we come across. The best way to manage technical debt-other than preventing it altogether-is to shine a light on it. That means documenting and cataloging it so you can (hopefully) sprinkle some debt management into your sprints later. If you don’t add debt to your backlog, it will quickly be forgotten.
Conclusion
Anti-patterns like Dead Code and Lava Flows are common pitfalls that seem harmless because after all the application may still be working correctly but over time they do more damage to a application than most bugs ever could. However, these patterns don’t have to be inevitable. By fostering a culture of ownership, refactoring, and unit testing, we can transform these challenges into opportunities for growth. It starts with each of us taking responsibility-not just for our own code, but for the health and quality of the entire system. Clean, maintainable code doesn’t just benefit today’s project; it sets the stage for the next developer and the future of the application. Tackling technical debt and embracing best practices is no easy task, but it’s the hallmark of a true professional. The road to a better codebase begins with a single step, and that step starts with you.
