The Dark Side Of Software: Anti-Patterns (and How To Fix Them)

Photo by sebastiaan stam / Unsplash

The Dark Side Of Software: Anti-Patterns (and How To Fix Them)

Behind every buggy app lies an Anti-Pattern waiting to be uncovered.

Paul Knulst  in  Programming Jan 31, 2025 18 min read

Introduction

An Anti-Pattern is a proven way to "shoot yourself in the foot." The term Anti-Pattern was coined by Andrew Koenig and it's pretty entertaining to read about it in the "Design Patterns: Elements of Reusable Object-Oriented Software", published in 1994. The author defines Anti-Patterns as a "commonly-used process, structure or pattern of action that, despite initially appearing to be an appropriate and effective "Response to a problem, has more bad consequences than good ones."

In 1998 the term "Anti-Pattern" became popular thanks to the book "AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis". It defined Anti-Patterns as "specific repeated practices in software architecture, software design, and software project management that initially appear to be beneficial, but ultimately result in bad consequences that outweigh hopes-for advantages."

The Dark Side Of Software: Anti-Patterns (and How To Fix Them) - Classification of Anti-Patterns in Software Design
Classification of Anti-Patterns in Software Design
πŸ’‘
In short: An Anti-Pattern is a common practice that has more bad consequences than good.

In the following article, we will learn about the reason why Anti-Patterns are used, the most common Anti-Patterns, and possible solutions that can be used to avoid these Anti-Patterns.

Reasons Anti-Patterns are used

An Anti-Pattern, similar to a design pattern, is a literary form and simplifies the communication and description of common problems. Often Anti-Patterns are a design pattern applied in the wrong context.

In Software Development, the following factors are often the main causes of Anti-Patterns.

  • Pressure of time
The app should work next week, we don't have time for unnecessary stuff.
  • Disinterest
I don't care if it looks good; it should work.
  • Closed-Mindedness
We always do it like this; we do not have to change anything.
  • Laziness
It's much faster doing it like this; let's clean up later.
  • Stinginess
We don't have more budget to do that.
  • Ignorance
Why should we change this? It will never need an update anyway.
  • Pride
I know what I do; my way is the best!

Countermeasures To Avoid Anti-Patterns

To counter these factors, software design and development must take the following fundamental forces into account when making decisions about the project:

  • Functionality Management
    • Ensure that the product delivers the features while balancing between scope and feasibility.
  • Complexity Management
    • Simplify design and solutions to reduce unnecessary complexity of the software. Also, improve maintainability.
  • Performance Management
    • Address non-functional requirements like development speed, reliability, and scalability to improve quality.
  • Change Management
    • Adopt a flexible strategy to handle new requirements and unexpected changes without restarting the project.
  • IT Resource Management
    • Allocate and utilize all technical resources efficiently.
  • Technology Transfer Management
    • Plan the adoption of new technologies and ensure smooth integration with existing systems.

Domains of Anti-Patterns

In the previously mentioned book "AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis" the Anti-Patterns are categorized into three different domains which cover most of the common areas where Anti-Pattern can be seen. However, nowadays, additional perspectives and domains were identified or created which also can have different Anti-Patterns.

πŸ’‘
The following list contains each domain from the book and others that I personally encounter the most while working as a Software Engineer and which I want to further explain by showing some Anti-Patterns for each domain

Software Development: This domain focuses on coding and implementation practices and its Anti-Patterns lead to poor software structure which makes it harder to maintain and extend.

Software Architecture: This domain is about system- and enterprise-level design which often leads to faulty or rigid architectures in the system. This avoids scalability and adaptability for further development.

Software Project Management: This is all about team coordination, resource allocation, and timelines. Often brought about by poor communication and bad leadership resulting in chaos and inefficiency.

Software Operations and Deployment: Anti-Patterns in this domain involve the deployment, monitoring, and scaling of software in production environments.

User Experience (UX) and Design: This domain describes the usability and accessibility of software in which ignoring UX often leads to not meeting user expectations.

Team Dynamics and Culture: This domain focuses on the human side of software development, including collaboration and morale. If not maintained it creates a toxic culture and poor team dynamics which can fail even well-structured projects.

Security and Compliance: In this domain security vulnerabilities and adherence to regulatory requirements are addressed because ignoring best practices or compliance requirements can result in security breaches or fines for the company.

Anti-Patterns In Software Development

The God Object

The God Object (or God class) is a commonly used Anti-Pattern which occurs if a single class/object has too many responsibilities, becoming the main part of the system and handles completely different functionality that normally should be part of multiple specialized classes. Additionally, many other classes often depend on the God class which creates high coupling.

How to solve: Use the Single Responsibility Principle and break down the God objects methods and properties into smaller classes/functions.

πŸ’‘
You can read this chapter of another article to better understand this common problem.

Spaghetti Code

If the code is unstructured and not organized you have created Spaghetti Code. The Software Developer cannot read and maintain it properly because it does not follow a straight line. Jumping between methods with many if conditions or having multiple criss-crossing dependencies.

How to solve: Adapt coding standards and start using meaningful names for classes. Avoid using circular usage of dependencies and start modularizing functionality

Copy-Paste Programming

Code is reused by simply copying other fragments within the project which leads to redundant code fragments often resulting in problems while maintaining these parts.

How to solve: Extract functionality into abstract/reusable functions, classes, or libraries. Use a Static analyzer (e.g. SonarQube) to find the redundancies.

Golden Hammer

A specific tool, framework, or pattern (often the singleton pattern) is used to "fix" every problem even if it does not fit.

How to solve: Identify the problem context and use the correct tool/pattern. Often this leads to an enormous amount of refactoring if done too late.

Magic Numbers

Within the codebase, multiple numbers and strings are used without any explanation often resulting in uncertainties about how to understand them.

How to solve: Replace magic numbers (or strings) with well-named constants to improve the readability of the code.

πŸ’‘
See this article's chapter for more information about Magic numbers in codebases.

Hard-Coded Values

The codebase uses fixed configuration values from a variable instead of retrieving them from a configuration file or through environment settings. This leads to the problem of an app being rebuilt whenever this setting changes.

How to solve: Use configuration files for different versions, environment variables for server configurations, and constants that load the data.

Overengineering

For simple solutions/problems, very complex designs are used that add unnecessary layers and features that aren't needed. In short, a codebase or a design that solves problems that you do not have.

How to solve: Focus on the current problem, develop lightweight and extensible solutions, and add features or layers only if necessary.

If it is stupid but it works, it isn't stupid. - Mercedes Lackey, Owlknight

Shotgun Surgery

If small modifications are needed to fix a bug or add a feature they often result in a very big change including multiple unrelated parts of the app.

How to solve: Refactor the codebase to improve cohesion and coupling by combining important functionality into a single class or module.

πŸ’‘
If this happens a lot in the codebase we should apply the SOLID principles to improve the overall code quality.

Lava Flow

When old undocumented or poorly documented code stays in the project because developers find it too risky to remove it the Lava Flow Anti-Pattern is present. Normally, this happens if a project is very old and has many legacy functionalities that no one understands and no one is able to learn. Often this results in a code state where many parts are nearly impossible to maintain and debugging or refactoring is very complicated.

How to solve: Analyze the legacy code, identify dependencies and interactions between classes/functions, and add documentation step-by-step. Write tests to verify that the legacy part does what it should do and check if it is really used by changing and verifying with the implemented tests. Then remove unnecessary parts from the project and ensure that no new bugs will be introduced. It would be very easy if tests had been developed before.

Dead Code

In contrast to Lava Flow the Dead Code Anti-Pattern describes code that is never executed or used in the app. Often, this results from refactoring or changing requirements where the new code is implemented but the developer doesn't remove the unused parts. These dead code areas increase the complexity of classes and create confusion for every developer.

How to solve: Run static code analysis tools like SonarQube to identify not used code. Also, modern IDEs like IntelliJ have built-in functionality to find unused parts (imports/functions/classes) in the Project. After finding them start refactoring/removing the unused parts. Additionally, integrate the code analysis tool into a CI/CD environment to prevent creating Pull Requests/Features with Dead Code.

YAGNI (You Aren’t Gonna Need It)

Sometimes features are added to projects that may be needed in the future. This adds clutter and increases the complexity without adding any value. Features shouldn't be in the codebase unless they are needed

How to solve: Focus on the requirements of the initial version and don't implement features that could be important in the future.

πŸ’‘
If already implemented, handle these unused features the same as you would handle Dead Code

Premature Optimization

Many developers tend to optimize their algorithms before a real performance bottleneck is identified. This leads to the high complexity of simple functions and sometimes introduces bottlenecks that were not present before optimizing

How to solve: Before refactoring identify critical issues and profile the performance. Also, prioritize developing clean, understandable, and maintainable code before optimization of old functions.

Anti-Patterns in Software Architecture

Silver Bullet Syndrome

The silver bullet syndrome describes the overuse of a single tool, technology, or methodology used as a universal solution to every problem regardless of the context.

πŸ’‘
For example, using MongoDB as a database although MariaDB/PostgreSQL is better for the specific service. Another example could be using the Waterfall model instead of an Agile approach for every project.

How to solve: Each problem should be evaluated individually to find the correct approach for every context. Additionally, all different kinds of technologies, tools, or methodologies should be researched to know when to choose which one.

Not Invented Here Syndrome (NIH)

The Not Invented Here Syndrome happens if a project team avoids using any third-party tools, framework, or solution simply because they are not invented by themself. The software will have many self-created solutions for common problems because architects struggle against software that is not developed by them.

How to solve: Keep your mind open and embrace external solutions when they are well-documented, tested, and fit the problem to avoid "reinventing the wheel". Also, this will save resources and money.

Big Ball of Mud

Having a system with no clear architecture, chaotic unmaintainable code that has no layers, no modularity, and no documentation is a "Big Ball of Mud". This normally results in non-maintainable software that has several bugs and performance issues.

How to solve: Introduce a clear architecture by refactoring it into smaller, cohesive modules and enforce standards like layered architecture or adopt a microservice approach.

Vendor Lock-In

If a software project uses a technology or a tool tied to a specific vendor and becomes completely dependent on their implementation the "Vendor Lock-In" Anti-Pattern is present. If the vendor makes software changes or expected product features are delayed the project will delay or will be unable to complete.

How to solve: Either use open standards from the beginning or create an isolation layer on top of the vendor's software to separate the project from the vendor's implementation. Later, the vendor can be exchanged and the isolation layer is the only part that has to be changed. Also, the isolation layer can combine multiple vendors to provide all the features that are needed for the project.

Anchors Aweigh

Anchors Aweigh occurs in a project when progress is hindered because of outdated or initial design decisions, tools, or technologies. The Anti-Patterns name refers to a ship that is unable to sail because the anchor is still set. During Software Development this anchor could be a prototypal wireframe or a missing dependency that is not delivered. Additionally, this could be because of a specific part of the app that is kept despite being used and generating multiple bugs.

How to solve: Either plan early, review architectural decisions periodically (and early), and encourage the team to refactor features incrementally. Also, invest in skill development so that developers can use newer technology and reduce resistance toward these technologies/tools.

Cargo Cult Programming

If developers start to use code or patterns simply because "it's a best practice" without fully understanding why or how the Patterns work they have established a "Cargo Cult Programming". This often results in overcomplication where unnecessary libraries, tools, or design patterns are used for problems that could be solved much more easily without them.

πŸ’‘
The term originates from the "cargo cults" in the South Pacific, where islanders imitated the behaviors of World War II soldiers (like building fake airstrips) in hopes of attracting planes with cargo.

How to solve: Developers should invest more time to understand the code, the tool, the pattern, or the framework before applying it to the app. Additionally, the company should encourage learning, collaboration, and advancement to create a culture where developers tend to ask "why". Furthermore, code reviews should be mandatory in which multiple developers have to review the solution before it is applied to the main code.

The Onion

If new code is developed to encapsulate existing poorly structured code instead of refactoring or optimizing the architecture "The Onion" Anti-Pattern is present. This results in a layered architecture structured like an onion where the core functionality becomes hard to access or change due to the wrapping. Also, adding new features often introduces abstraction layers instead of addressing the main issues. In the end, the app's performance will suffer and become unmaintainable.

How to solve: Refactor or replace the core parts of the system and move them into modules. Instead of wrapping layers into new layers start refactoring and treat this refactoring as part of the roadmap instead of putting it into the "nice-to-have" section.

Anti-Patterns in Project Management

Broken Window Theory

The Broken Windows Theory describes acceptance of small code issues like poor naming, some unused code, or using bad practices (copy/paste). The problem is that these small issues will accumulate over time resulting in a chaotic, unmaintainable codebase. Often this problem arises from the project manager who decides "to not do this now" because other features are more important and the customer wants it.

How to solve: Instead of accepting small issues developers have to fix them immediately either by themself or by using automated tools like static code analysis tools (SonarQube). Furthermore, clean code standards have to be established so that many of these issues can be addressed. Code reviews in which these guidelines are checked should be done by all developers.

Death March

The Death March Anti-Pattern describes projects that are doomed from the start due to unrealistic requirements or deadlines. This normally includes over-optimistic planning and poor risk management resulting in a lack of trust of developers and pressure from stakeholders or management. Also, company culture is based on control instead of trust where opinions and warnings from experts are ignored.

How to solve: Use empirical evidence to prove why deadlines or requirements could not be fulfilled. Communicate early with stakeholders and set clear understandable expectations where unrealistic features are stopped at the start. As the project manager encourage the team to talk openly about risk and also listen to their concerns about the project.

Analysis Paralysis

If too much time is spent by decision-makers analyzing, discussing, and planning an "Analysis Paralysis" occurs which prevents progress. Often, endless meetings are conducted without concrete solutions because everyone fears deciding the wrong thing. In these meetings simple problems are getting too much focus and very unlikely scenarios are discussed all the time instead of prioritising core functionality. The decision-makers try to find the perfect solution to the problem instead of creating a practical one that works. This leads to missed deadlines, wasted resources, and a demotivated team of engineers.

How to solve: The most important step to do is setting a deadline for the decision which limits the time for discussions. If not already done the project team should adopt agile principles and start with a small, quick release and increment based on feedback instead of planning the perfect solution before the start. This means the team should focus on an MVP (minimal viable product).

Scope Creep

Scope Creep happens if the project requirements change all the time by extending the features or adding changes after the project plan or concept was agreed on. Additionally, these new changes don't adjust the time, budget, and staffing of the project which leads to missed deadlines, exceeding budget, overworked teams, and more importantly a lower software product.

How to solve: Define a detailed project scope and document all requirements that are approved by all stakeholders. If changes are needed use Change Request and evaluate the change regarding budget, timelines, and resources. If adding features is still needed communicate early with the client and explain that these additional features will increase costs and deadlines.

Brooks' Law

Brooks' Law states:

Adding manpower to a late software project makes it later.
- Fred Brooks in his book The Mythical Man-Month

In a project, this means if the features take too much time adding a new developer does not save time, it instead causes further delays because the onboarding has to take place where the new team members need to be taught the codebase, the tools, and everything project-related. Additionally, the code quality will suffer because the team will rush to integrate the new developer which will lead to less understanding of the codebase and therefore introduce more bugs and inconsistencies. Also, the experienced developers have to interrupt their tasks to train newcomers which will lower their productivity.

How to solve: To avoid Brooks' Law the project team has to improve their planning and estimation to avoid hitting deadlines. If this still fails, the software teams should scale gradually by adding software developers early and onboarding them properly and not at the near end of the project. Start by using pair programming early to integrate them. Another possibility would be to break down the task into independent units where adding people to work on specific parts will not interfere because they can be done in parallel without having dependencies

Anti-Patterns in Software Operations and Deployment

Focus: Deployment and runtime considerations.

Snowflake Server

In Software Operations, a Snowflake Server is a special unique server that was manually configured by someone and has no documentation which makes it very difficult to reproduce the configuration, scale the server, or recover from an incident. As everything was done manually for the server another server often does not have the exact same configuration which leads to inconsistencies and problems if deploying software. This manual configuration also increases the risk of downtime because if something happens, the engineer has to find out what to do to recover.

How to solve: To face this problem tools like Terraform or Ansible (Infrastructure as Code) should be used to guarantee that every server is the same, e.g. has the same versions, packages, and tools installed. Additionally, all server changes (or migrations) should be kept in a version-controlled repository (like GIT) and everything should be applied using a CI/CD pipeline to automatically update/deploy software. To be fully flexible the used infrastructure should be immutable which means that containerization like Docker or Kubernetes is used to host services.

Manual Deployment

Manual Deployment describes the steps that are done to release new software packages. These steps often involve manually copying files, running db scripts by hand, and connecting to the server infrastructure through SSH. This process is usually very time-consuming and vulnerable to human error because a typo can cause heavy failures. Also, sometimes there could be inconsistencies between different environments, such as Dev/Staging/Demo/Release. Sometimes, if done manually, there is no backup strategy implemented which makes it really difficult to roll back to a working version if the deployment fails or breaks something.

How to solve: If making changes to multiple (or even single) systems automated deployments should be used. To do this, implement a proper CI/CD tool such as Jenkins, GitHub Actions, GitLab CI, ArgoCD, or Woodpecker (that's what I use). Using this kind of tool will guarantee that each deployment uses the same steps and normally there should be no human error. Additionally, a rollback strategy could be implemented in the CI/CD pipeline to have a production-ready disaster recovery without having manual interaction.

Anti-Patterns in User Experience (UX) and Design

Mystery Meat Navigation

The Mystery Meat Navigation Anti-Pattern is from the book "Web pages that suck" by Vincent Flanders and is an Anti-Pattern that describes an interface that is confusing because it has several buttons, links, or other things that don't have informative labels and forces the user to hover and click to find out how it works. This increases the time the users waste on the web page (or app) and will lead to frustration because of the bad experience resulting in very high bounce rates and lower engagement.

πŸ’‘
I guess everyone already saw an UI where the Mystery Meat Navigation Anti-Pattern was present. I woul love to get some examples. Please write them down in the comments!

How to solve: Instead of having unspecified icons for navigation use clear labels with descriptive text or a tooltip to show the user what happens if the specified link is clicked. Furthermore, apply state-of-the-art UI guidelines and conventions that are recognizable and recurring for multiple apps (e.g. Material Design for apps). Additionally, every developer should prioritize Web Accessibility which will ensure that screen readers can use the app/website without any problems.

Dark Patterns

Dark Patterns describes a process or misleading UI/UX practices to manipulate users into taking actions they don't want to do such as subscribing to a newsletter, sharing the data, or spending money. Luckily, many countries have introduced laws against these shady tactics (EU Digitial Services Act).

How to solve: If wanted this can be solved by being transparent and clearly offering pricing, and terms. Communicate with the user and optimize the opt-out mechanism by providing easy one-click links that will unsubscribe from the subscription. Also, explain all fees and offer the ability to cancel early without making the user feel guilty for opting out (e.g. don't give them bad feelings for canceling).

Anti-Patterns in Team Dynamics and Culture

Hero Syndrome

In a team with multiple developers, the Hero Syndrome occurs if a single team member thinks he is "the expert" for everything and the only one who can solve the most complex problems. Although that person is highly skilled their behavior prevents collaboration and improvement of other developers. This creates knowledge silos because only the hero knows about all kinds of problems/projects and if he leaves or gets ill this knowledge is gone. Furthermore, this leads to the problem, that knowledge is not shared among others and non-hero developers don't want to take ownership of tasks. Also, the hero often works more than he has to which could lead to burnout.

How to solve: The most important thing is to share the knowledge between all developers by doing pair programming and adding detailed documentation for every task. Also, promote collaboration by doing discussions, collecting decisions, and evaluating all of them prior to implementation. Develop a mentorship program in which the Senior/Principal developer takes their time to mentor Junior or new developers. Keep in mind that this process only works if the hero wants to do it.

Blame Culture

Everyone makes mistakes, but in a Blame Culture, these mistakes are used to punish the developer which means that if a mistake is made other developers do finger-pointing at the one who did it wrong. This results in a toxic environment where developers don't take the initiative for new tasks and maybe hide mistakes because of fear. The problem is that this hinders innovation, damages the morale of the team, and increases the stress.

How to solve: Establish a learning culture where mistakes are a natural part of growth and use retrospectives to focus on the solutions and improvements instead of blame. Create an environment where people feel safe and can make mistakes without fear. Promote taking responsibility for tasks with the support of all team members and avoid punishing failure.

Anti-Patterns in Security and Compliance

Security Through Obscurity

Security Through Obscurity is an Anti-Pattern where a system is secured by a hidden design or infrastructure that is not publicly documented. This means that the source code is hidden, the password scheme is unbelievably complex, and the details of the security system are confidential. The reason to do this is to discourage the attackers from exploiting the system that they don't know. However, this is not a security mechanism because nowadays attackers are constantly looking for new ways to exploit apps or websites. And if they break the obscurity, it is often very easy to exploit the weakness.

How to solve: Instead of relying on obscurity rely on best practices for security guidelines to implement strong authentication/encryption. On a yearly (or monthly) basis conduct audits and penetration tests to actively check the system for vulnerabilities. Manually review code and use of techniques to secure the app. At least observe the OWASP Top Ten vulnerabilities/risks and avoid creating them in your app.

Checkbox Compliance

If a company only focuses on meeting the minimum regulatory requirements to secure an app rather than addressing all kinds of risks, the Anti-Pattern Checkbox Compliance occurs. The name Checkbox Compliance is used because security risks and compliance are often noted down in a list and before releasing this list has to be "checked" to pass the audits. Unfortunately, attackers do not care about these guidelines, and if the app still has major security issues it will be vulnerable to them.

How to solve: Establish a mindset of teams to prioritize security as a fundamental part of the software they develop. Observe the OWASP Top Ten and go beyond company regulations to focus on real threats that are relevant to the app. Integrate Penetration testing, code reviews, and code analyzer tools in the creation of software and educate developers on security best practices by investing in cybersecurity training.

Closing Notes

recognizing and understanding Anti-Patterns in multiple areas of software development is critical for creating efficient, maintainable, secure, and extensible systems. Each domain mentioned in this article provides its own set of issues, but the fundamental problem is the same: poor practices in development, architecture, management, operations, user experience, team dynamics, and security. All of them can have long-term negative consequences for software quality and organizational success.

Identifying these typical problems allows teams to take proactive actions to prevent them, cultivating a culture of continuous development, collaboration, and best practices. The key to conquering anti-patterns is in:

  • Awareness: entails recognizing the indications and symptoms of antipatterns early on.
  • Proactive prevention: entails putting in place strategies, tools, and processes to reduce risks before they become ingrained.
  • Adaptability: means being open to change, feedback, and incremental improvement.
  • Strong Leadership and Culture: Promoting teamwork, information sharing, and accountability.

Additionally, everyone should understand that no project is created without any Anti-Pattern. But with an open mind to face software development and non-toxic management, it is possible to identify Anti-Patterns, refactor the software, and optimize the process which leads to better software and a friendly work environment for everyone.

I hope this article gave you a quick and neat overview of Anti-Patterns.

I would love to hear your feedback about this list. Furthermore, if you already used Anti-Patterns, or found and refactored some, please comment here and explain what you have done. Also, if you have any questions, please ask them in the comments. I try to answer them if possible.

Feel free to connect with me on MediumLinkedInTwitter, BlueSky, and GitHub.


β˜•

πŸ™Œ Support this content

If you like this content, please consider supporting me. You can share it on social media, buy me a coffee, or become a paid member. Any support helps.

See the contribute page for all (free or paid) ways to say thank you!

Thanks! πŸ₯°

By Paul Knulst

I'm a husband, dad, lifelong learner, tech lover, and Senior Engineer working as a Tech Lead. I write about projects and challenges in IT.