Design Patterns in Python

Master proven solutions to recurring software design problems. Design patterns are the vocabulary of experienced developers, providing tested blueprints for building maintainable and scalable systems.

What Are Design Patterns?

Design patterns are reusable solutions to common problems that appear repeatedly in software design. They are not code you can copy and paste. Instead, they are templates and strategies that guide how you structure your classes and objects to solve specific challenges. Patterns give developers a shared vocabulary: when someone mentions "Factory" or "Observer," everyone immediately understands the architecture being discussed.

The canonical reference is the "Gang of Four" book, which categorizes 23 patterns into three groups. Creational patterns handle object creation in ways that suit the situation. Structural patterns organize classes and objects into larger, more flexible structures. Behavioral patternsdefine how objects communicate and distribute responsibility. Beyond these classics, Python has its own idioms like mixins, descriptors, and context managers that solve problems in ways uniquely suited to the language.

Singleton Pattern

The Singleton pattern ensures that a class has exactly one instance and provides a global point of access to it. This is useful for managing shared resources like configuration settings, database connections, or logging systems where multiple instances would cause conflicts or waste resources. In Python, the most Pythonic way to implement a singleton is by overriding the__new__ method, which controls instance creation before__init__ runs.

Singleton Implementation

Python

One instance, accessed globally throughout the application.

Factory Pattern

The Factory pattern delegates object creation to a separate function or method rather than calling constructors directly. This is useful when the exact type of object to create depends on input parameters, configuration, or runtime conditions. The client code does not need to know about the specific classes being instantiated. It just asks the factory for an object and receives one that implements the expected interface.

Factory Method

Python

Creating different transport objects based on type.

Abstract Factory Pattern

The Abstract Factory pattern provides an interface for creating families of related objects without specifying their concrete classes. This is one level of abstraction above the simple Factory. Instead of a single factory method, you have an entire factory object that produces a suite of related products. This is useful when your system needs to work with different variants of a product family, like UI components for different operating systems or themes.

Abstract Factory for UI Themes

Python

Creating coordinated sets of UI components.

Builder Pattern

The Builder pattern constructs complex objects step by step. Instead of a constructor with a long parameter list, you call methods on a builder object to set each part of the final object. This improves readability and lets you create different representations of the same object using the same construction process. Builders are especially useful when an object has many optional parameters or requires validation during construction.

Builder for a Complex Query

Python

Constructing SQL-like queries step by step.

Prototype Pattern

The Prototype pattern creates new objects by copying an existing object (the prototype) rather than instantiating from scratch. This is useful when object creation is expensive or when you want to clone an object that has a complex state. Python'scopy module provides both shallow and deep copying, making this pattern straightforward to implement.

Prototype with Deep Copy

Python

Cloning objects with nested mutable state.

Adapter Pattern

The Adapter pattern allows two incompatible interfaces to work together. It acts as a bridge by wrapping one object and translating its interface into the one expected by the client. This is useful when integrating third-party libraries or legacy code that you cannot modify directly. The adapter converts the existing interface without changing the underlying implementation.

Adapter for Legacy Code

Python

Making an old API compatible with a new interface.

Decorator Pattern

The Decorator pattern adds new behavior to objects dynamically without modifying their structure. Decorators wrap the original object and intercept calls to its methods, adding functionality before or after delegation. This is different from Python's function decorators (the @ syntax), though the concept is similar. The pattern is useful when you need multiple optional features that can be combined in any order.

Decorator for Coffee Orders

Python

Adding features dynamically to a base object.

Facade Pattern

The Facade pattern provides a simplified interface to a complex subsystem. Instead of exposing dozens of classes and methods, the facade presents a single, unified API that handles the most common use cases. This reduces the learning curve for clients and decouples them from internal complexity. If the subsystem changes, only the facade needs to be updated, not every client.

Facade for Home Automation

Python

One method to control multiple subsystems.

Proxy Pattern

The Proxy pattern provides a placeholder or surrogate for another object to control access to it. Proxies are used for lazy loading (delaying expensive operations until needed), access control (checking permissions before delegation), or logging and monitoring. The proxy implements the same interface as the real object, so clients interact with it in exactly the same way.

Virtual Proxy for Lazy Loading

Python

Delaying expensive resource initialization until first use.

Composite Pattern

The Composite pattern lets you treat individual objects and compositions of objects uniformly. It is used to represent tree structures where each node can be either a leaf (individual item) or a composite (container of other items). Both implement the same interface, so clients can work with them without knowing whether they are dealing with a single item or a group. This is common in UI frameworks (widgets containing other widgets) and file systems (files and directories).

Composite for File System

Python

Treating files and directories uniformly.

Observer Pattern

The Observer pattern defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. This decouples the subject from its observers, allowing you to add or remove observers without modifying the subject. The pattern is foundational in event-driven systems, GUIs, and reactive programming.

Observer for Stock Price Updates

Python

Notifying multiple subscribers when data changes.

Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The strategy lets the algorithm vary independently from the clients that use it. Instead of a long conditional statement choosing behavior, you inject the desired strategy object. This is particularly clean in Python, where strategies can be simple functions or callable objects.

Strategy for Payment Methods

Python

Selecting payment algorithm at runtime.

Command Pattern

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undo operations. Each command object contains all the information needed to execute an action. This decouples the sender of a request from the receiver and makes it easy to add new commands without changing existing code.

Command Pattern with Undo

Python

Encapsulating operations as objects.

Iterator Pattern

The Iterator pattern provides a way to access elements of a collection sequentially without exposing the underlying structure. In Python, this pattern is built into the language through the iterator protocol. Any object that implements__iter__ and__next__can be iterated over with a for loop. Generators provide an even simpler way to create iterators using theyield keyword.

Custom Iterator

Python

Implementing the iterator protocol manually.

Template Method Pattern

The Template Method pattern defines the skeleton of an algorithm in a base class but lets subclasses override specific steps without changing the overall structure. The template method calls a series of abstract or hook methods that subclasses implement. This enforces a consistent process while allowing customization at specific points.

Template Method for Data Processing

Python

Fixed workflow with customizable steps.

Chain of Responsibility Pattern

The Chain of Responsibility pattern passes a request along a chain of handlers. Each handler decides whether to process the request or pass it to the next handler in the chain. This decouples the sender from the receiver and allows multiple objects a chance to handle the request. It is commonly used in logging systems, event handling, and middleware pipelines.

Chain of Responsibility for Support Tickets

Python

Passing requests through a chain of handlers.

Mixin Pattern

A mixin is a class that provides methods intended to be inherited by other classes but is not meant to stand alone. Mixins add specific functionality without being part of the primary inheritance hierarchy. They are a form of multiple inheritance used to compose behavior from reusable pieces. Python's support for multiple inheritance makes mixins a natural and powerful pattern.

Mixins for Serialization

Python

Adding JSON and XML export capabilities.

Descriptor Pattern

Descriptors are a low-level mechanism that control attribute access. Any object that defines__get__,__set__, or__delete__is a descriptor. They are used to implement properties, methods, class methods, and static methods under the hood. Custom descriptors let you add validation, type checking, or lazy computation to attributes in a reusable way.

Descriptor for Validation

Python

Enforcing constraints on attribute assignment.

Context Manager Pattern

Context managers handle setup and teardown logic automatically using thewith statement. They implement__enter__ and__exit__methods. This pattern ensures resources like files, network connections, or locks are properly released even if an exception occurs. Python'scontextlibmodule provides decorators to create context managers from generators.

Custom Context Manager

Python

Managing resource lifecycle with __enter__ and __exit__.

Borg Pattern

The Borg pattern is a Python variation of Singleton. Instead of ensuring only one instance exists, Borg allows multiple instances but makes them share the same state. All instances of a Borg class have the same__dict__, so changing an attribute on one instance changes it for all. This can be more Pythonic than a strict Singleton because it does not restrict instantiation.

Borg Pattern Implementation

Python

Multiple instances, shared state.

SOLID Principles in Python

SOLID is an acronym for five design principles that make software more maintainable and scalable. These principles guide how to structure classes and manage dependencies, and they work hand-in-hand with design patterns.

The Five SOLID Principles

Guidelines for writing clean, maintainable object-oriented code.

  • Single Responsibility: A class should have one reason to change. Each class does one thing well.
  • Open/Closed: Classes should be open for extension but closed for modification. Add behavior through inheritance or composition, not by editing existing code.
  • Liskov Substitution: Subclasses should be substitutable for their base classes without breaking the program.
  • Interface Segregation: Clients should not depend on interfaces they do not use. Split large interfaces into smaller, more specific ones.
  • Dependency Inversion: Depend on abstractions, not on concrete implementations. High-level modules should not depend on low-level modules.

Single Responsibility Principle

Python

One class, one job.

Anti-patterns to Avoid

Anti-patterns are common approaches that seem reasonable at first but lead to problems down the line. Recognizing them is as important as knowing good patterns. Here are some of the most frequent mistakes in object-oriented Python.

Common Anti-patterns

Patterns that look like solutions but create more problems.

  • God Object: A single class that knows or does too much. Split responsibilities across focused classes.
  • Circular Dependencies: Module A imports B, B imports A. Refactor to remove the cycle or use late imports.
  • Overuse of Global State: Relying on global variables makes testing and reasoning about code difficult. Prefer dependency injection.
  • Premature Optimization: Applying complex patterns before understanding the problem. Start simple and refactor when the need becomes clear.
  • Magic Numbers and Strings: Hardcoding values throughout the code. Use named constants or configuration objects.
  • Deep Inheritance Hierarchies: More than 2-3 levels of inheritance usually signal a problem. Prefer composition.

Avoiding God Object Anti-pattern

Python

Split responsibilities into cohesive classes.

When to Use Design Patterns

Design patterns are tools, not requirements. Use them when they clarify your design and solve a real problem. Do not force a pattern into a situation where a simple function or class would suffice. The best code is often the simplest code that works. Patterns shine when complexity is unavoidable and structure is needed to manage it.

Start by writing straightforward code. When you notice duplication, tight coupling, or difficulty adding features, consider whether a pattern addresses the root cause. Study how established libraries and frameworks use patterns in practice. Real-world codebases teach you when patterns help and when they add unnecessary ceremony.

Quiz - Test Your Knowledge

Eight questions covering creational, structural, behavioral, and Python-specific patterns, plus SOLID principles. Test your understanding of when and how to apply each pattern effectively.

Knowledge Check

1. What is the primary purpose of the Singleton pattern?

2. Which pattern is best suited for creating objects when the exact type is determined at runtime?

3. What problem does the Decorator pattern solve?

4. In the Observer pattern, what is the relationship between subjects and observers?

5. What distinguishes the Borg pattern from the Singleton pattern?

6. Which SOLID principle states that a class should have only one reason to change?

7. What is the primary benefit of using the Strategy pattern?

8. What is a Python mixin?