Do design patterns increase or decrease the complexity of an Application?

Introduction

Design patterns are well-known in software development. They offer ready-made solutions to common problems and give developers a shared way to talk about code. When used wisely, they can make applications easier to organize, extend, and work on as a team.

But the real question is: do they always simplify software, or can they, in certain contexts, make it harder to understand, maintain, and evolve? This tension between elegance and overengineering lies at the heart of the debate explored in this article “Do Design Patterns Increase or Decrease the Complexity of an Application?”.

Complexity or not complexity ?

  • If the person reading the code is aware of design patterns and their concept and is able to identify design patterns in practical use (not just the book examples) then they really do reduce the complexity.
  • In the first approach, if you suddenly have more classes or code to go through to solve what seems to be a simple problem at first. If you’re not aware of the benefits of design patterns, hacked solutions always look better.
  • Design patterns often lead to additional levels of abstraction around a problem, and if not handled correctly then too much abstraction can lead to complexity.
  • However, since design patterns provide a common vocabulary to communicate ideas they also reduce complexity and increase maintainability.
  • There’s an infamous disease known as “Small Boy With A Pattern Syndrome” that usually strikes someone who has recently read the GoF book for the first time and suddenly sees patterns everywhere. That can add complexity and unnecessary abstraction.
  • In the short term, design patterns will often increase the complexity of the code. They add extra abstractions in places they might not be strictly necessary. However, in the long term they reduce complexity because future enhancements and changes fit into the patterns in a simple way. Without the patterns, these changes would be much more intrusive and complexity would likely be much higher. Take for example a decorator pattern. The first time you use it, it will add complexity because now you have to define an interface for the object and create another class to add the decoration. This could likely be done with a simple property and be done with. However, when you get to 5 or 20 decorations, the complexity with a decorator is much less than with properties.
  • To be specific, many patterns increase accidental complexity of a design by introducing new structures (interfaces, methods, etc.) that weren’t present in the design before the pattern was applied.
  • Let’s use Visitor as an example.

    Visitor is a way of separating operations from an object structure on which they operate. Before the solution with Visitor, the operations are hard-coded into each Element of the object structure. The challenge for the developer is that adding new operations involves modifying the code in the various elements.

    After the application of the Visitor pattern, there is an additional class hierarchy of visitors, which encapsulate the operations. The flow of control in the solution is definitely more complex, and will be harder to debug (anyone who has implemented Visitor and tried to follow the program flow of double-dispatched calls with accept/visit will know this).

    Understanding and maintaining Visitor functions in terms of cohesive units is less complicated than the alternative of coding functions into each of the Elements in the fixed structure that is visited. This is the benefit of the pattern.

    It’s difficult to say quantitatively how much increase there is in accidental complexity or how much easier it is to add new operations. I certainly don’t agree with answers that make a blanket statement saying in the long-term, complexity is reduced with applying a pattern. It’s not like your design “forgets” the double-dispatch added by Visitor’s approach, just because you have code which more easily allows operations to be added. The complexity is a price (or tax) you pay to get the benefit in maintainability.

    Patterns still have to be applied

    Regardless of one’s supposed familiarity with patterns, any given pattern must be applied to a solution. That application is going to be different every time (Martin Fowler said patterns are only half-baked solutions). Developers will always have to understand what classes are playing what roles in the existing design, which is subject to the essential complexity (the application problem’s complexity) that is often non-trivial.

    In the best case, understanding a design pattern applied in an application that’s already complex may be trivial, but it’s not 0 effort:

    • Patterns aren’t always applied the same way. There are many variants of patterns — Proxy comes to mind. I’m not sure that everyone agrees about how any given pattern should be applied.
    • Introducing one pattern (e.g., Strategy to encapsulate algorithms) often leads to other patterns to manage things properly (e.g., Factory to instantiate the concrete Strategies).
    • Introducing a pattern often leads to more responsibilities. Object cleanup when a Factory is used is not trivial (and also not documented in GoF). How many know about the so-called Lapsed-listener problem?
    • What happens if there is a change in the assumptions made about the need for the pattern (e.g., there is no longer a need to have multiple encapsulated algorithms provided by the Strategy pattern)? It’s going to be extra work to remove the pattern later. If you don’t remove it, new developers could be duped by its presence when they come on board. Patterns are intertwined between the classes playing the roles in the pattern. Removal is not trivial.

    Erich Gamma gave an anecdote at ECOOP 2006 that designers in one case decided to remove the Abstract Factory pattern from a commercial multi-platform GUI widget framework (the classic Abstract Factory example!). It turned out that the multiple-levels of indirection (polymorphic calls) in complex GUIs was a significant performance hit in the client code. Customers complained about GUIs being sluggish, and the “optimization” was to remove the indirections. In this case, performance trumped maintainability; the pattern was only making the coders happy, not the end users.

    Revolving door metaphor

    Using buildings as a metaphor, let’s consider a revolving door as a building design pattern. The following image comes from Wikipedia:

You can “see” the additional complexity in such a door. The benefits of revolving doors are in energy savings. They attempt to solve the problem where there are people frequently going in and out of a building, and opening/closing a standard door allows too much air to be exchanged between the inside the outside of the building each time.

It probably wouldn’t make sense to install a revolving door as the entrance of a two-bedroom house, because there is not enough traffic to justify the additional complexity. The revolving door would still work in a two-bedroom house. The benefits in terms of energy savings would be small (and might actually be worse because of size and air-tightness relative to a conventional door). The door would surely cost more and would take up more space than a traditional door.

  • Design Patterns work like algorithms for Object Oriented Programming. Shows you how to put together objects in a meaningful relationship that performs a particular task. So I would say yes they reduce complexity by allowing you to understand the design of the software better. The same way with algorithms in procedural programs. If I told you that X was using a Linked List with a Bubble Sort it would be a lot easier to following along with what the programmer was doing.

A Practical Example: My Experience at Alcatel

During my time at Alcatel, I encountered a large and already complex codebase filled with an abundance of design patterns: Strategy, Decorator, Factory, Observer—you name it. At one point, a new feature needed to be introduced, and instead of looking for the simplest path, the reflex was to add yet another pattern, almost as if it was mandatory.

What should have been a relatively straightforward integration quickly became a matter of introducing more abstraction, more indirection, and more layers of complexity. Maintenance became heavier, the visibility of the code declined, and understanding the flow of logic required navigating through a forest of interfaces, abstract classes, and wrappers. The complexity was not in the problem itself, but in the solution chosen.

Lessons Learned

From that experience, I realized a few key points:

  • Introducing a design pattern is not always the best answer, even if it looks elegant on paper.

  • Simplicity often leads to better maintainability and faster onboarding for new team members.

  • Before adopting a new pattern, it’s crucial to balance long-term benefits (reusability, scalability, testability) against the added overhead in terms of abstractions, dependencies, and cognitive load.

In the end, design patterns are tools—not goals in themselves. Their value lies not in their presence, but in their judicious and pragmatic use.

The Effect of Design Patterns on Object-Oriented Metrics and Software Error-Proneness

  • In the literature, design patterns are mainly known as reusable solutions for different problem contexts. But, application of design patterns has different effects on software. One of them is expected to be the effect on software error-proneness.
  • The effects of design patterns on software are measurable and therefore interpretable.
  • Design pattern implementations and refactorings in software are highly recommended. Also, design patterns have positive effects on design, and increase reusability, analyzability, and so on.
  • But, design patterns may introduce side effects like increase in error-proneness.
  • Conclusion from a thesis from Barış Aydınöz : software error-proneness related OO metrics have been determined and reviewed. Moreover, design patterns that affect these metrics have been found out, and their effect on metrics have been shown empirically on individual experiments. 13 different cases have been refactored with design patterns, and fluctuations on metrics have been evaluated mainly from the perspective of the software errorproneness. Also, in this thesis, [GOF98] patterns have been researched and the effects of these patterns on OO metrics have been measured with refactorings….   …software error-proneness related OO metrics have a significant impact on software development cycle. Since, by using coupling and inheritance metrics, very accurate models can be derived to guess error-prone classes in software [BDPW98-2]. These models will obviously help developers to reduce software testing phase of the development cycle. Design patterns, as stated before, are elegant and reusable solutions to common occurring design problems. But, this study showed that design patterns may have many effects on software quality.

The Influence of AI on Design Patterns

With the rapid rise of artificial intelligence in software development, the way we think about design patterns is also evolving. Traditionally, patterns have been static, human-crafted solutions to recurring problems. But today, AI-assisted tools can suggest which pattern fits best in a given context, or even generate boilerplate implementations automatically. This lowers the barrier to using patterns correctly and reduces the risk of misapplication.

Moreover, AI is driving the emergence of new types of patterns. For example:

  • The Model Deployment Pattern: widely used in machine learning, it defines how trained models can be packaged, versioned, and served reliably in production. This pattern didn’t exist in classical software engineering but is now essential in AI-driven applications.

  • The Data Pipeline Pattern: AI systems rely heavily on continuous flows of data. This pattern organizes the collection, cleaning, transformation, and feeding of data into models, ensuring consistency and reproducibility across environments.

AI can also assist in applying classic design patterns more effectively. For instance, consider a scenario where a developer must choose between the Strategy pattern and the State pattern. An AI-powered tool can analyze the code context, the expected variability of behavior, and the frequency of state transitions to suggest the most suitable pattern. This not only speeds up decision-making but also reduces the likelihood of introducing unnecessary complexity.

In this sense, AI not only makes existing design patterns easier to apply, but also inspires the creation of entirely new ones. The usefulness of patterns remains the same—providing structure, clarity, and shared understanding—but their scope is expanding to cover domains that were not part of traditional software engineering.

Conclusion

Design patterns have long been foundational in software engineering, offering standardized solutions to recurring problems. However, with the advent of artificial intelligence, these patterns are evolving. AI not only facilitates the application of traditional design patterns but also inspires the creation of new ones tailored to the unique challenges of AI systems. For instance, patterns like the Model Deployment Pattern and the Data Pipeline Pattern address specific needs in AI-driven applications, ensuring efficient data flow and seamless model integration.

Moreover, AI is transforming the way we approach software development. Tools like Figma’s Model Context Protocol (MCP) and Google’s Stitch enable developers to generate code from natural language prompts or visual designs, streamlining the development process and enhancing creativity. These advancements underscore the importance of a thoughtful approach: while AI can be a powerful ally, it requires careful integration to avoid unnecessary complexity.

Further Reading

For a deeper understanding of AI-driven design patterns and the evolving landscape of coding with AI, consider exploring the following resources:

  • AI Design Patterns: Understanding RAG Pattern
    An introduction to AI design patterns, focusing on the RAG pattern and its applications in AI system development.
    Read more

  • Practical Design Patterns for Modern AI Systems
    A comprehensive guide discussing repeatable solutions for common challenges in AI-driven software, categorized into five key areas: Prompting & Context, Responsible AI, User Experience, AI-Ops, and Optimization Patterns.
    Explore the patterns

  • Generative AI Design Patterns: A Comprehensive Guide
    An in-depth look at design patterns for generative AI, based on evaluations of numerous production implementations of large language models (LLMs).
    Learn more

  • AI Agentic Design Patterns with AutoGen
    A course offering insights into building and customizing multi-agent systems using the AutoGen framework, enabling agents to take on different roles and collaborate to accomplish complex tasks.
    Enroll here

  • Exploring Software Design Patterns with AI: Future Trends
    An exploration of how timeless design patterns are being infused with new dynamism through AI, enhancing efficiency, scalability, and resilience in projects.
    Read the article

These resources provide valuable insights into the intersection of AI and software design, offering practical guidance for developers navigating this evolving field.

Key Takeaways

In this study, the relationships between software error-proneness associated metrics and design patterns have been investigated. Additionally, this study emphasizes the importance of design patterns, the lack of standard metric analysis tools, the lack of standard ways for hatching design patterns, and how emerging AI-assisted tools can influence both the selection and creation of design patterns in modern software development.