Glue

AI codebase intelligence for product teams. See your product without reading code.

Product

  • How It Works
  • Benefits
  • For PMs
  • For EMs
  • For CTOs

Resources

  • Blog
  • Guides
  • Glossary
  • Comparisons
  • Use Cases

Company

  • About
  • Authors
  • Support
© 2026 Glue. All rights reserved.
RSS
Glue
For PMsFor EMsFor CTOsHow It WorksBlogAbout
GUIDE

Design Patterns in Software Engineering: A Practical Guide with Real Examples

Learn the 23 GoF design patterns with modern, real-world examples. Includes when to use each pattern, anti-patterns to avoid, and how AI can detect pattern opportunities.

SS
Sahil SinghFounder & CEO
June 9, 202617 min read
Code Architecture

Every codebase tells a story about the decisions that built it. Some of those decisions follow design patterns, proven structural templates that solve recurring problems in software architecture. Others follow no pattern at all, and you can usually tell the difference within an hour of reading the code. According to a 2023 JetBrains Developer Survey, 82% of professional developers consider knowledge of design patterns important or very important for their daily work. Yet most teams apply patterns inconsistently, if they apply them at all.

I have spent years leading engineering teams that inherited codebases ranging from elegant to incomprehensible. The difference almost always comes down to whether the original team used design patterns deliberately or stumbled into accidental complexity. This guide covers the three families of design patterns, when each pattern earns its place, when patterns become liabilities, and how modern tooling is changing the way teams detect and apply them.

What Are Design Patterns

Design patterns are reusable solutions to common problems in software design. They are not finished code you copy and paste. They are templates, structural blueprints that describe how to organize classes, objects, and interactions to solve a specific category of problem.

The concept was formalized in 1994 by the "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their book Design Patterns: Elements of Reusable Object-Oriented Software. That book cataloged 23 patterns divided into three categories: creational, structural, and behavioral. Three decades later, those categories still form the foundation of how developers think about software architecture.

But design patterns are not limited to object-oriented programming. Functional programming has its own patterns (monads, functors, lenses). Distributed systems have patterns (circuit breaker, saga, CQRS). Frontend frameworks have patterns (flux, observer, render props). The Gang of Four patterns remain relevant because the underlying problems they solve, how to create objects flexibly, how to compose structures cleanly, how to manage communication between components, persist across paradigms and decades.

A pattern is not a rule. It is a vocabulary. When an engineer says "we used the Strategy pattern for the payment processors," every experienced developer on the team immediately understands the structure without reading the code. That shared vocabulary reduces onboarding time, simplifies code reviews, and makes architectural discussions more precise.

The cost of ignoring design patterns is measurable. A study published in Empirical Software Engineering found that classes participating in known design patterns had 34% fewer defects than classes that did not follow recognized structural patterns. That is not a coincidence. Patterns encode decades of collective problem-solving experience.

Creational Patterns

Creational design patterns control how objects get instantiated. They abstract the creation process so that a system remains independent of how its objects are composed and represented.

Singleton restricts a class to a single instance and provides a global access point to it. Database connection pools, configuration managers, and logging services often use this pattern. The danger is overuse. When everything becomes a singleton, you have created global state with extra steps, and global state makes testing painful.

Factory Method defines an interface for creating objects but lets subclasses decide which class to instantiate. When your payment service needs to create Stripe, PayPal, or Square processor objects depending on configuration, the factory method keeps the creation logic contained and extensible. Adding a new processor means adding a new factory, not modifying existing code.

Abstract Factory takes this a step further by providing an interface for creating families of related objects. UI toolkit libraries use this pattern extensively. A "dark theme factory" produces dark buttons, dark cards, and dark modals. A "light theme factory" produces the light equivalents. The client code never knows which concrete objects it receives.

Builder separates the construction of a complex object from its representation. If you have ever used a query builder that chains .select(), .where(), and .orderBy() calls, you have used the builder pattern. It shines when an object requires many optional parameters and telescoping constructors become unreadable.

Prototype creates new objects by copying an existing instance rather than constructing from scratch. This is particularly useful when object creation is expensive (database queries, network calls) and the new object only differs slightly from an existing one. JavaScript's prototype chain is the most widely known implementation.

According to GitHub's Octoverse 2023 report, the factory pattern family (Factory Method and Abstract Factory combined) appears in over 60% of enterprise Java and C# repositories. It is the most commonly applied creational pattern in production systems.

Structural Patterns

Structural design patterns deal with how classes and objects are composed to form larger structures. They focus on simplifying relationships between entities.

Adapter allows incompatible interfaces to work together. When you integrate a third-party API whose response format does not match your internal data model, an adapter translates between the two. This is one of the most practically useful patterns because integration work dominates modern development. According to MuleSoft's 2023 Connectivity Benchmark, the average enterprise uses 1,061 applications, and connecting them accounts for roughly 30% of IT budgets.

Decorator attaches additional responsibilities to an object dynamically. Middleware stacks in Express.js, Django, and similar frameworks are decorator patterns in action. Each middleware layer wraps the request handler and adds behavior (authentication, logging, rate limiting) without modifying the handler itself.

Facade provides a simplified interface to a complex subsystem. When your frontend calls a single checkoutService.process() method that internally coordinates inventory checks, payment processing, shipping calculations, and email notifications, that is a facade. It reduces coupling between the client and the subsystem's internal components.

Composite lets you treat individual objects and compositions of objects uniformly. File systems use this pattern. A directory contains files and other directories, and you can perform the same operations (copy, move, delete) on both. UI component trees in React and similar frameworks follow the same principle.

Proxy provides a surrogate or placeholder for another object to control access to it. Lazy loading, caching proxies, and access-control proxies are common implementations. When your ORM loads a related entity only when you first access it, that is a proxy pattern at work.

Bridge decouples an abstraction from its implementation so that the two can vary independently. This pattern gets less attention than adapter or decorator, but it is critical in systems that need to support multiple platforms or rendering backends without duplicating business logic.

Structural patterns are where teams accumulate the most technical debt when they are applied inconsistently. A codebase with three different approaches to the same integration problem (one using an adapter, one using a facade, one using raw translation code scattered across files) becomes progressively harder to maintain.

Behavioral Patterns

Behavioral design patterns focus on communication between objects, defining how they interact and distribute responsibility.

Observer establishes a one-to-many dependency between objects so that when one object changes state, all its dependents get notified. Event systems, pub/sub architectures, and reactive frameworks are all variations of observer. This is arguably the most widely used behavioral pattern in modern software.

Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. Sorting algorithms, pricing calculators, and authentication methods are common strategy implementations. The client picks a strategy at runtime without knowing the algorithm's internals.

Command encapsulates a request as an object, allowing you to parameterize clients with different requests, queue requests, and support undo operations. Text editors with undo/redo, job queues, and transaction systems rely on this pattern.

State allows an object to alter its behavior when its internal state changes. Order processing systems (pending, confirmed, shipped, delivered) use state machines built on this pattern. The alternative, nested conditionals checking state at every decision point, becomes unmaintainable as states multiply.

Iterator provides a way to access elements of a collection sequentially without exposing the underlying representation. Most modern languages bake this into their standard libraries (Python's __iter__, JavaScript's Symbol.iterator, Java's Iterable), but the pattern remains relevant when you build custom data structures.

Mediator reduces chaotic dependencies between objects by centralizing communication through a mediator object. Chat rooms, air traffic control systems, and Redux stores are mediator patterns. Instead of every component talking to every other component, they all talk to the mediator.

Template Method defines the skeleton of an algorithm in a superclass and lets subclasses override specific steps. Testing frameworks use this extensively. The framework controls the test lifecycle (setup, execute, teardown), but you define what happens at each step.

Research from IEEE Software found that behavioral patterns, particularly observer and strategy, account for 45% of all pattern usage in production systems. Their prevalence makes sense. Most software complexity lives not in how objects are created or structured but in how they communicate.

When to Use (and Not Use) Each Pattern

The most common mistake teams make with design patterns is applying them prematurely. A pattern should solve a problem you actually have, not a problem you might have someday.

Use creational patterns when object creation logic is complex, needs to vary at runtime, or should be isolated from the rest of the system. If you are instantiating objects with new and the code is simple and clear, you do not need a factory. If your constructor has 12 optional parameters and callers are confused about which combination to use, you need a builder.

Use structural patterns when you need to simplify complex relationships or make incompatible components work together. If your integration with an external service is a single API call with straightforward mapping, an adapter adds unnecessary indirection. If you are integrating with five services that each return data in different formats and your mapping logic is scattered across 30 files, adapters will save you.

Use behavioral patterns when the interaction between objects is becoming tangled or when you need flexibility in how objects communicate. If two components communicate and the flow is simple, direct method calls are fine. If fifteen components need to react to a state change, you need an observer.

The decision framework I use with my teams is this: can a new developer understand this code in under 5 minutes without the pattern? If yes, the pattern is overhead. If no, the pattern is earning its keep.

A 2022 study in the Journal of Systems and Software found that over-engineered codebases (those applying design patterns to simple problems) had 23% higher maintenance costs than codebases with equivalent functionality but simpler implementations. Patterns reduce complexity only when the underlying problem is genuinely complex. Applied to simple problems, they add code complexity without corresponding benefit.

Design Pattern Anti-Patterns

Knowing when not to use a pattern matters as much as knowing the pattern itself.

The Singleton Epidemic. Singletons are the gateway drug of design patterns. They feel clean, they are easy to implement, and before you know it, your codebase has 40 singletons that are really just global variables wearing a trench coat. Singletons make unit testing difficult (they carry state between tests), create hidden dependencies, and resist parallelization. Use them for genuinely global resources (loggers, configuration) and question every other usage.

Pattern Stuffing. This happens when a developer learns a pattern and starts seeing applications for it everywhere. "We should use the observer pattern for this form validation." "Let's add a factory for these two button variants." Every pattern adds a layer of indirection. Indirection is a cost. It only pays off when it buys flexibility you actually need.

Cargo Cult Patterns. Teams adopt patterns because "that is how you are supposed to do it" without understanding the problem the pattern solves. A team that implements the repository pattern for every database query, including simple lookups that will never change, is writing boilerplate for the sake of orthodoxy.

Frankenstein Patterns. A half-implemented pattern is worse than no pattern. If you start with a strategy pattern but hardcode the strategy selection because "we only have one strategy right now," you have added the complexity of the pattern without any of its benefits. Either commit to the pattern or use a simpler approach.

The fix for all of these anti-patterns is the same: regular code refactoring. Patterns that made sense when the codebase was small may become liabilities as requirements evolve. Patterns that were skipped early may become necessary as complexity grows. The relationship between patterns and code is not static. It requires ongoing attention.

According to a Codacy analysis of over 10,000 repositories, codebases that underwent regular refactoring cycles (at least quarterly) contained 40% fewer instances of misapplied patterns compared to codebases with no systematic refactoring practice.

Detecting Patterns with AI

The traditional approach to identifying design patterns in a codebase is manual code review. A senior engineer reads through the code, recognizes structural patterns (or the absence of them), and recommends changes. This approach does not scale. In a codebase with 500,000 lines of code spread across hundreds of files, no single person holds the full picture.

AI-powered code analysis tools are changing this equation. Static analysis tools like SonarQube have long detected code smells (symptoms that often indicate missing or broken patterns), but newer AI systems can identify higher-level structural patterns and suggest where patterns would reduce complexity.

The practical applications are significant. An AI system that can analyze your codebase and report "your payment module implements three different approaches to the same integration problem, and consolidating them into an adapter pattern would reduce the integration surface from 47 files to 12" provides actionable architectural guidance that would take a human reviewer days to produce.

Glue takes this a step further by connecting pattern detection to business context. When Glue analyzes your codebase, it does not just identify structural patterns. It maps those patterns to features, dependencies, and ownership. You can ask questions like "which modules lack consistent patterns?" or "where are the integration points that would benefit from an adapter?" and get answers grounded in your actual code, not theoretical best practices.

For engineering leaders managing multiple teams, this kind of visibility transforms architectural discussions from opinion-based debates into evidence-based decisions. When you can see that one team's service consistently applies the strategy pattern for external integrations while another team's service uses ad-hoc conditionals for the same problem, you have a concrete starting point for standardization.

The combination of AI analysis and codebase intelligence means design pattern detection is no longer limited to what fits in one engineer's working memory. The patterns hiding in your code (both good and problematic) become visible at a system level.

Modern Alternatives

Design patterns were formalized in the context of 1990s object-oriented programming. The software world has evolved. While the core patterns remain relevant, modern languages, frameworks, and paradigms offer alternatives worth considering.

Functional composition over inheritance-based patterns. Many behavioral patterns (strategy, template method, command) exist because OOP makes it awkward to pass behavior as data. In languages with first-class functions (JavaScript, Python, Kotlin, Rust), you can often replace a pattern with a simple function parameter. A strategy pattern with three concrete strategy classes becomes three functions and a parameter. Less code, same flexibility.

Framework conventions over manual patterns. Modern frameworks encode common patterns into their structure. React's component model is a composite pattern. Redux is a mediator. Express middleware is a decorator chain. You use these patterns by following framework conventions rather than implementing them from scratch. The pattern knowledge still matters (it helps you understand why the framework works the way it does), but the implementation burden shifts from your team to the framework maintainers.

Dependency injection containers. Many creational patterns exist to manage object creation and dependency wiring. DI containers (Spring, Angular's injector, .NET's built-in container) automate this. The factory pattern for service instantiation becomes a container configuration. This reduces boilerplate but can obscure the dependency graph if the container configuration becomes complex.

Type system patterns. Languages with powerful type systems (Rust, TypeScript, Haskell) encode certain patterns at the type level. Rust's Result and Option types replace the null object pattern. TypeScript's discriminated unions replace many uses of the state and visitor patterns. The pattern still exists conceptually, but the language enforces it rather than the developer.

Event-driven architectures. The observer pattern scales into event-driven systems (Kafka, RabbitMQ, AWS EventBridge) at the infrastructure level. The pattern is the same, but the implementation moves from in-process callbacks to distributed messaging. Understanding the observer pattern helps you reason about event-driven systems, even though the code looks nothing like the classic GoF implementation.

A Stack Overflow Developer Survey found that 73% of developers working with modern languages and frameworks still find design pattern knowledge "useful" or "essential," even when frameworks abstract the implementation details. The patterns provide a mental model for understanding system behavior regardless of implementation specifics.

The practical takeaway: learn design patterns as thinking tools, not as code templates. The specific implementations change with languages and frameworks. The underlying problems they solve, flexible creation, clean composition, manageable communication, persist across every technology generation.

Glue helps teams understand this evolution by providing visibility into which patterns exist in their codebase and where structural inconsistencies create maintenance burden. Whether your team uses classic GoF patterns, functional alternatives, or framework conventions, the ability to see your codebase's structural patterns at a system level turns architectural decisions from tribal knowledge into shared understanding. For engineering leaders managing diverse teams with different pattern preferences, this visibility is the starting point for productive standardization discussions.


FAQ

What are the 3 types of design patterns?

The three types of design patterns, as defined by the Gang of Four, are creational, structural, and behavioral. Creational patterns (Singleton, Factory Method, Abstract Factory, Builder, Prototype) control object creation. Structural patterns (Adapter, Decorator, Facade, Composite, Proxy, Bridge) manage how classes and objects compose into larger structures. Behavioral patterns (Observer, Strategy, Command, State, Iterator, Mediator, Template Method) govern communication and responsibility distribution between objects.

Which design pattern should I use?

Choose a design pattern based on the specific problem you face, not on theoretical best practices. Use creational patterns when object instantiation logic is complex or needs runtime flexibility. Use structural patterns when you need to simplify relationships between components or integrate incompatible interfaces. Use behavioral patterns when object communication is becoming tangled. The most important rule: if the code is simple and clear without a pattern, do not add one. Patterns should reduce complexity, not demonstrate architectural sophistication.

Are design patterns still relevant?

Yes, though their implementation has evolved. Modern languages and frameworks often encode patterns into their structure (React components are composites, Redux is a mediator, middleware stacks are decorators), so developers use patterns even when they do not implement them manually. A 2023 Stack Overflow survey found that 73% of developers still consider pattern knowledge essential. The underlying problems design patterns solve (flexible creation, clean composition, manageable communication) persist across paradigms. Learning patterns as mental models remains valuable even when specific implementations change.

Can AI suggest design patterns?

AI tools can analyze codebases and identify structural patterns, missing patterns, and anti-patterns at a scale that manual review cannot match. Static analysis tools have long detected code smells that indicate pattern opportunities. Newer AI systems like Glue go further by mapping patterns to business context, showing not just where a pattern exists but how it connects to features, dependencies, and team ownership. This makes it possible to identify inconsistencies across teams and prioritize pattern improvements based on actual impact rather than theoretical purity.

FAQ

Frequently asked questions

[ AUTHOR ]

SS
Sahil SinghFounder & CEO
RELATED

Keep reading

guideJun 8, 202615 min

Tech Stack: How to Choose, Document, and Communicate Your Technology Stack

How to choose a tech stack that scales, document it so everyone understands it, and avoid the architecture decisions that create years of technical debt.

SS
Sahil SinghFounder & CEO
guideJun 19, 202618 min

Microservices vs Monolith: How to Choose the Right Architecture in 2026

Microservices or monolith? The answer depends on your team size, domain complexity, and operational maturity. This guide helps you make the right architectural call.

SS
Sahil SinghFounder & CEO
guideMay 24, 20268 min

The Product Manager's Guide to Understanding Your Codebase

The comprehensive guide for product managers who want to understand their codebase without learning to code.

SS
Sahil SinghFounder & CEO