51 companion flashcards · AI-assisted study content · Open the deck →
This deck walks you through the world of reusable solutions in software design, covering the classic Gang of Four (GoF) patterns that have shaped modern programming practices. You'll find cards on every major category—creational, structural, and behavioral patterns—with questions ranging from foundational concepts to the specific intent, mechanics, and trade-offs of each pattern. Whether you're tackling Singleton, Factory Method, Builder, Adapter, Decorator, or others, the cards are designed to build both recognition and deeper understanding of why each pattern exists.
It's a great fit for students preparing for technical interviews, developers deepening their object-oriented design skills, or anyone studying for a software engineering exam. Even experienced programmers can use it as a refresher on patterns they may not reach for every day, helping you recognize the right tool for problems involving object creation, structure, or behavior delegation.
To get the most out of your study sessions, try focusing on one pattern family at a time rather than mixing everything up front. Pay extra attention to cards that highlight differences between similar patterns, like comparing Abstract Factory to Factory Method, since those nuances often come up in interviews. Finally, space your reviews across several days—patterns click best when you revisit them just as you're about to forget, reinforcing the mental models rather than just the definitions.
Design patterns are reusable solutions to problems that recur again and again in software design. Rather than being finished code that can be dropped into a project, patterns are templates or blueprints that describe how to solve a particular kind of problem, leaving the developer to adapt them to the surrounding context. The concept was popularized by the so-called Gang of Four (GoF) — Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides — in their influential 1994 book. Because patterns capture wisdom that has been tested across many projects, they offer several practical benefits: they give developers a shared vocabulary for talking about design, they promote proven approaches over reinvented solutions, and they tend to produce code that is easier to maintain and more flexible when requirements change.
Design patterns are usually grouped into three broad categories based on what kind of problem they address. Creational patterns deal with object creation mechanisms, controlling how objects are instantiated in ways that fit the situation. Structural patterns deal with object composition and relationships, showing how classes and objects can be combined to form larger structures. Behavioral patterns deal with communication between objects, defining how responsibilities are distributed and how objects interact. Familiar representatives include Singleton and Factory Method for creational concerns, Adapter and Decorator for structural concerns, and Observer and Strategy for behavioral concerns.
Patterns are best understood as conceptual tools, not goals in themselves. The aim is to recognize recurring forces in a design and apply the pattern whose intent matches those forces. A good way to start is by asking what varies in the system and how that variation can be encapsulated. When in doubt, simpler code without explicit patterns is often the better starting point, with patterns introduced through refactoring as complexity grows.
Creational patterns focus on how objects are created, aiming to make instantiation flexible, decoupled, and appropriate to the situation. The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. Typical implementations use a private constructor so clients cannot freely instantiate the class, combined with a static method that returns the shared instance. Initialization can be eager, with the instance created when the class loads, or lazy, with the instance created on first use. Singleton is appropriate when exactly one instance is genuinely needed, such as a configuration manager, a connection pool, or a logger. However, it introduces global state, which makes testing harder, and it can violate the Single Responsibility Principle because the class manages its own lifecycle. Thread safety requires careful handling, and the pattern is often considered an anti-pattern when overused; a modern alternative is to manage a single shared instance through a dependency injection container.
The Factory Method pattern defines an interface for creating an object but lets subclasses decide which concrete class to instantiate. A creator class declares a factory method that returns a product, and each subclass overrides it to produce a specific variant. This pattern follows the Open/Closed Principle, because new product types can be added by introducing new subclasses without modifying existing code, and it decouples client code from the concrete classes it depends on. It is useful when a class cannot anticipate the class of objects it must create, when subclasses should specify the objects being created, or when you want to localize the knowledge of which class gets instantiated, as in a logistics application where one creator builds trucks and another builds ships.
The Abstract Factory pattern goes a step further by providing an interface for creating families of related objects without specifying their concrete classes. A typical example is a GUI toolkit factory with methods that create buttons, checkboxes, and menus, where concrete factories then produce coordinated sets of widgets for different operating systems. The key distinction from Factory Method is that Abstract Factory uses composition — a factory object creates multiple related products — whereas Factory Method uses inheritance to produce a single product through a subclass. Abstract Factory often relies on Factory Methods internally to construct each product in the family.
The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. Instead of a constructor with many parameters, sometimes called the telescoping constructor problem, the client configures a builder step by step and then asks it to produce the final object. This is especially helpful when an object has many optional fields, when construction must follow a particular order, or when different representations of the same kind of object need to be produced, such as complex SQL queries, programmatic UI layouts, or test fixtures with many optional fields. The Prototype pattern takes yet another approach, creating new objects by cloning an existing one rather than invoking a constructor. This avoids the cost of building from scratch and hides the complexities of creating new instances; in Java it relies on the Cloneable interface, while in C++ it is typically implemented with copy constructors. Prototype is most useful when objects differ only slightly or when direct construction is expensive.
Structural patterns describe how to assemble classes and objects into larger structures while keeping those structures flexible and efficient. The Adapter pattern converts the interface of a class into another interface that clients expect, allowing otherwise incompatible classes to work together. A common analogy is a power plug adapter that lets a US plug fit a European socket. There are two main forms: a class adapter, which relies on multiple inheritance to adapt one interface to another, and an object adapter, which uses composition to wrap an existing class and is generally preferred. The Bridge pattern decouples an abstraction from its implementation so the two can vary independently. A Shape abstraction paired with a Color implementation, for instance, can produce combinations such as RedCircle or BlueSquare without needing a class for every possible combination, preventing a cartesian product explosion of subclasses when multiple dimensions of variation exist.
The Composite pattern composes objects into tree structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly. A file system, where both files and directories implement a common component interface, is a classic example; the interface lets client code call the same operation on leaves and on groups of leaves alike. The Decorator pattern attaches additional responsibilities to an object dynamically, offering a flexible alternative to subclassing. A decorator wraps the original object, implements the same interface, and adds behavior before or after delegating to the wrapped object. Java's I/O streams are a familiar example, where buffering, character conversion, and other features are layered on top of a basic file input stream. Decorators are useful when responsibilities need to be added dynamically without affecting other objects, when subclassing would explode the number of classes, or when behaviors should be combined flexibly at runtime, as in middleware stacking or adding logging, caching, or authentication to services.
The Facade pattern provides a simplified interface to a complex subsystem without adding new functionality, making existing functionality easier to use. A HomeTheaterFacade, for example, might expose a watchMovie method that internally turns on the projector, dims the lights, and starts the player, reducing coupling between clients and the underlying components. The Flyweight pattern reduces memory usage by sharing common state among many objects while keeping unique state external. Intrinsic state, such as the font used to render a character, can be shared across all character objects, while extrinsic state, such as position, is supplied by the client at use time. This pattern is appropriate when an application must support a very large number of similar objects and most of their state can be made extrinsic. The Proxy pattern provides a surrogate or placeholder for another object to control access to it, with common variants including virtual proxies that delay the creation of expensive objects, protection proxies that check permissions, remote proxies that represent objects in a different address space, and caching proxies that store results of expensive operations.
Several of these patterns share structural similarities but differ in intent, and understanding the distinctions is essential for choosing among them. Adapter and Facade both wrap other classes, but an Adapter makes an existing interface compatible with another expected interface and typically wraps a single class, while a Facade simplifies a complex subsystem made up of many classes — Adapter is about interoperability, Facade about convenience. Bridge and Adapter also share structure, but an Adapter is a retrofit applied after a system is designed to bridge incompatible interfaces, whereas a Bridge is a planned decoupling introduced upfront to let abstraction and implementation vary independently. Composite and Decorator both rely on recursive composition, but Composite focuses on representing part-whole hierarchies while Decorator focuses on adding behavior to individual objects. Finally, Decorator and Proxy both wrap an object and implement the same interface, but Decorator adds new responsibilities chosen by the client, whereas Proxy controls access without necessarily adding behavior and is typically transparent to the client.
Behavioral patterns are concerned with how objects communicate and how responsibilities are distributed among them. The Observer pattern defines a one-to-many dependency between objects so that when one object, the subject, changes state, all of its dependents, the observers, are notified automatically. This pattern shows up in GUI event listeners, publish-subscribe messaging systems, and reactive programming libraries. It is the right choice when changes in one object require updating an unknown number of others, when the subject should not be tightly coupled to those it notifies, and when an event-driven architecture is desired, as in MVC model-view updates or message brokers.
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, letting the algorithm vary independently from the clients that use it. A context object holds a reference to a strategy and delegates the actual work to it; clients can swap strategies at runtime. This eliminates the conditional statements that would otherwise be needed to select behavior and makes it easy to add new algorithms without modifying existing code, with sorting strategies, payment processing options, and compression methods as common examples. The State pattern is structurally similar but solves a different problem: it allows an object to alter its behavior when its internal state changes, making the object appear to change its class. A TCP connection, for example, behaves differently when established, listening, or closed; each state is a separate class implementing a common interface, replacing complex if/else or switch statements with polymorphism. In Strategy, the client chooses which algorithm to use and the strategies are typically stateless; in State, the object itself transitions between states, and states often know about each other.
The Command pattern encapsulates a request as an object, letting clients be parameterized with different requests and supporting queuing, logging, scheduling, and undo. A typical command interface exposes execute and undo methods, and concrete commands know how to perform and reverse their actions. Common uses include undo and redo functionality in editors, transaction rollback in databases, macro recording, and remote control buttons mapped to device operations. The Template Method pattern defines the skeleton of an algorithm in a base class, deferring some steps to subclasses that override specific steps without changing the algorithm's overall structure. The base class controls the flow and calls subclass hooks, embodying the Hollywood Principle, often phrased as "don't call us, we'll call you."
The Iterator pattern provides a way to access elements of a collection sequentially without exposing its underlying representation, offering a uniform traversal interface that supports multiple simultaneous traversals and hides internal data structure complexity. The Mediator pattern encapsulates how a set of objects interact, with a mediator object handling communication between them so that participants do not refer to each other directly; an air traffic control tower is a typical example, turning many-to-many relationships into simpler one-to-many ones. The Chain of Responsibility pattern passes a request along a chain of handlers, where each handler either processes the request or forwards it to the next; middleware in web frameworks like Express and Laravel is a familiar application, with senders decoupled from receivers and processing chains composed dynamically. The Visitor pattern lets you add new operations to existing object structures without modifying the classes of the elements on which it operates, relying on double dispatch in which an element accepts a visitor and then calls the visitor's method for its own type; this makes adding new operations easy but adding new element types expensive, because every visitor must be updated. Finally, the Memento pattern captures and externalizes an object's internal state so it can be restored later without violating encapsulation: an originator produces state snapshots stored in mementos that a caretaker manages, supporting undo features, game checkpoints, and transaction rollback.
The SOLID principles are five guidelines that help developers build object-oriented systems that are maintainable, flexible, testable, and resistant to gradual decay. The Single Responsibility Principle states that a class should have only one reason to change, meaning it should have just one job or responsibility. A class that handles authentication, database persistence, and email sending clearly violates SRP; splitting it into a UserAuthenticator, a UserRepository, and an EmailService makes each class easier to understand, easier to test, and less tightly coupled to the others.
The Open/Closed Principle says that software entities should be open for extension but closed for modification. Instead of editing existing code to add new behavior, you should extend it through inheritance, composition, or interfaces. A common violation is adding if/else branches every time a new type appears in a system; the Strategy pattern is a typical remedy, allowing new algorithms to be plugged in without changing the context that uses them. The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without altering the correctness of the program. Subtypes must honor the supertype's contract: preconditions cannot be strengthened, postconditions cannot be weakened, and invariants must be preserved. The classic violation is Square extends Rectangle, where setting the width of a square unexpectedly changes its height.
The Interface Segregation Principle advises that clients should not be forced to depend on interfaces they do not use, preferring many small, specific interfaces over one large general-purpose one. An interface Worker that declares both work and eat methods forces every implementation, including a Robot, to provide an eat method it does not need; splitting the interface into Workable and Feedable solves the problem and reduces unnecessary dependencies.
The Dependency Inversion Principle has two parts: high-level modules should not depend on low-level modules — both should depend on abstractions — and abstractions should not depend on details, but details should depend on abstractions. In practice, instead of an OrderService depending directly on a MySQLDatabase, both depend on a DatabaseInterface that MySQLDatabase implements. DIP is a principle, while Dependency Injection is a technique for achieving it: dependencies are provided from outside rather than created internally, using constructor injection, setter injection, or interface injection. DI containers such as Spring, Laravel's service container, and the .NET dependency injection system automate this wiring. Together, the SOLID principles form a coherent foundation: SRP keeps classes focused, OCP keeps systems extensible, LSP preserves substitutability, ISP keeps interfaces lean, and DIP decouples high-level policy from low-level detail.
Several broader design principles complement the individual patterns and the SOLID guidelines. Composition over Inheritance favors assembling objects through has-a relationships rather than inheriting behavior through is-a ones. Composition avoids tight coupling to parent classes, allows behavior to be changed at runtime, prevents deep and fragile inheritance hierarchies, and makes it easier to substitute mock objects in tests. Many of the patterns already discussed, including Strategy, Decorator, Observer, and Bridge, embody this principle by relying on composition rather than inheritance to extend behavior.
The Hollywood Principle captures the idea that high-level components should control the flow of a program and call low-level components, rather than the reverse. It is often phrased as "don't call us, we'll call you." Template Method demonstrates this by having a base class call subclass hooks, while Observer does so by having the subject call its observers, and Dependency Injection does so by having a framework call into application code. The closely related idea of Inversion of Control generalizes the same idea: control flow is inverted so that a framework calls your code instead of your code calling the framework. Common implementations include Dependency Injection, Template Method, and event-driven systems, and IoC containers such as Spring, Laravel, and the .NET DI host manage this inversion for entire applications.
Anti-patterns are common solutions that look attractive but are actually counterproductive. The God Object, sometimes called God Class, is one of the most damaging: a single class that knows too much and does too much, with thousands of lines of code, dozens of methods spanning different concerns, and many dependencies. It violates SRP by definition. The fix is to extract responsibilities into separate classes following SRP, optionally introducing a Facade to preserve a simplified entry point for clients, and refactoring incrementally with tests as a safety net. Other well-known anti-patterns include Spaghetti Code, where logic is tangled and unstructured; the Golden Hammer, in which a single familiar solution is applied to every problem; Lava Flow, in which dead code is left in place because nobody dares to remove it; and Copy-Paste Programming, where code is duplicated instead of being properly abstracted. Recognizing these patterns is the first step toward refactoring them away.
Choosing the right pattern is a matter of judgment rather than rule-following. The most useful guideline is to identify what varies in a design and encapsulate that variation, which often points directly to one or two candidate patterns. Match the problem to a pattern's intent rather than its structure, and resist the temptation to force a pattern into a place where it does not solve a real problem. The SOLID principles serve as a useful compass for narrowing down the choice. Start simple and refactor toward patterns only as complexity grows; over-engineering with patterns is itself an anti-pattern, sometimes called Pattern-itis. In the end, patterns are tools that serve clear thinking about software design, not ends in themselves.
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}Shape (abstraction) and Color (implementation) — a RedCircle combines both without creating a class for every combination.TCPConnection that behaves differently when in Established, Listening, or Closed states.if/else or switch statements with polymorphism. Each state is a separate class implementing a common interface.handler1.setNext(handler2);
handler2.setNext(handler3);
handler1.handle(request);Square extends Rectangle — setting width on a Square unexpectedly changes height.Drill this topic
51 flashcards on Design Patterns — free, no signup needed to start.
Study Design Patterns flashcardsLearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.