šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Python Design Patterns

Strategy, Factory, Observer, and Decorator — the classic Gang of Four patterns, implemented the way they actually look in idiomatic Python, which is often simpler than their textbook Java form.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

Why can Python's Strategy Pattern often skip defining a Strategy interface class entirely?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Design patterns were named and cataloged in a 1994 book written against Java- and C++-style languages. Python's first-class functions, duck typing, and built-in decorator syntax mean several of them collapse into something much simpler here. This lesson covers four patterns you'll actually see in real Python codebases, in their genuinely idiomatic form.

1Strategy and Factory: Patterns That Get Simpler in Python

The Strategy Pattern's textbook form (from the 1994 Gang of Four book, written against Java and C++) defines a Strategy interface, then a separate concrete class per algorithm variant, because those languages have no way to pass a bare function around as a first-class value — you need an object wrapping it just to hand it to another piece of code. Python has no such restriction: functions are ordinary values, storable in variables, passable as arguments, callable directly. checkout(100, apply_percent) *is* the Strategy Pattern, fully realized, with zero interface boilerplate — swapping apply_percent for apply_fixed at the call site is exactly 'swap the algorithm at runtime', the pattern's entire purpose.

The Factory Pattern translates more directly, because its core idea — centralizing object-creation logic so callers don't need to know which concrete class to instantiate — is still valuable in Python; a create_parser("json") function hides JsonParser behind a stable, simple interface, letting the concrete class set grow (adding create_parser("yaml")) without any caller needing to import or even know about the new class directly. Where Python simplifies this pattern is in the implementation: a plain function (often using match/case, as covered earlier in this module) usually replaces the more ceremonial 'AbstractFactory + ConcreteFactory classes' structure Java-style implementations often reach for.

The throughline: both patterns exist to solve real design problems (swappable behavior, centralized creation), and those problems are just as real in Python — but Python's language features (first-class functions, match/case) frequently let you solve them with dramatically less structural ceremony than the pattern's original textbook form implies is necessary.

āœ•
—
+
def apply_percent(total): return total * 0.9
def apply_fixed(total): return max(0, total - 10)

def checkout(total: float, strategy) -> float:
    return strategy(total)

checkout(100, apply_percent)  # 90.0
checkout(100, apply_fixed)    # 90.0 -- just pass a different function
localhost:3000
Simplified Strategy
checkout(100, apply_fixed)
The entire Strategy Pattern — one function parameter

2Observer: Decoupling Publishers From Subscribers

The Observer Pattern solves a specific coupling problem: a piece of code that produces events (a publisher) shouldn't need to know, at write time, everything that might eventually want to react to those events (subscribers) — new subscribers should be addable without ever modifying the publisher. EventBus.publish() only knows it can call each registered callback with an event; it has no import, no reference, no knowledge whatsoever of what bus.subscribe(lambda e: print(f"Logged: {e}"))'s specific lambda actually does.

This decoupling is what makes the pattern valuable in real systems: a logging subscriber, an analytics subscriber, and a notification subscriber can all register independently, be added or removed at runtime, and none of them — nor EventBus itself — needs to know the others exist. Compare this to a publisher that directly calls log_event(e); send_analytics(e); notify_user(e) inline: every new reaction requires editing the publisher directly, exactly the kind of modification-for-extension OCP (from the SOLID lesson) warns against.

Python's Observer implementations often use plain callables (functions, lambdas, or bound methods) as subscribers, exactly as shown here, rather than requiring subscribers to implement a formal Observer interface class — another case of duck typing collapsing ceremony that a statically-typed language's version of the pattern requires. EventBus doesn't care *what* a subscriber is, only that calling it with one argument works.

āœ•
—
+
def create_parser(file_type: str):
    match file_type:
        case "csv": return CsvParser()
        case "json": return JsonParser()
        case _: raise ValueError(f"Unknown type: {file_type}")

parser = create_parser("json")  # caller doesn't need to know JsonParser exists
localhost:3000
Decoupled Publishing
bus.publish("user_signed_up")
EventBus never references any specific subscriber's logic

3Decorator: The Pattern Python Built Into Its Syntax

The Decorator Pattern's purpose — attach additional responsibilities to an object dynamically, without altering its own source code or affecting other instances of the same class — describes, almost word for word, exactly what Python's @decorator syntax (covered in depth in the Decorators lesson) does for functions. with_logging wraps process_order, adding a logging responsibility, without a single line inside process_order itself changing — this is the Decorator Pattern, not merely 'inspired by' it or 'similar to' it.

This is a genuinely useful realization for reading design-patterns literature written for other languages: where a Java codebase might implement the Decorator Pattern as a class implementing the same interface as the object it wraps, holding a reference to that wrapped object, and delegating most calls while adding behavior around specific ones — Python gets the same structural benefit from a function wrapping a function, made ergonomic enough that the language gave it dedicated syntax.

Recognizing this pattern-to-language-feature mapping generalizes: understanding the *problem* each classic pattern solves (swappable behavior, centralized creation, decoupled event handling, dynamically added responsibilities) is more durable knowledge than memorizing each pattern's textbook Java implementation, precisely because different languages solve the same underlying problems with different amounts of ceremony — and Python, deliberately, tends to need less.

āœ•
—
+
class EventBus:
    def __init__(self):
        self._subscribers: list = []
    def subscribe(self, callback) -> None:
        self._subscribers.append(callback)
    def publish(self, event) -> None:
        for callback in self._subscribers:
            callback(event)

bus = EventBus()
bus.subscribe(lambda e: print(f"Logged: {e}"))
bus.publish("user_signed_up")
localhost:3000
Built-In Pattern
@with_logging
The Decorator Pattern, expressed as native language syntax

4Step-by-Step Breakdown

The 'Strategy Pattern' in Java is a whole file of interfaces and classes. In Python, it's sometimes just... a function. Let's see why.

Strategy Pattern: swap an algorithm at runtime. In Python, a first-class function often replaces an entire 'Strategy interface + concrete classes' hierarchy.

Checkpoint: Why can Python's Strategy Pattern often skip defining a Strategy interface class entirely?

  • →Functions are first-class objects in Python, so a plain function already satisfies "something callable with this signature"
  • →Python does not support the Strategy Pattern at all

Factory Pattern: centralize object creation logic so callers don't need to know which concrete class to instantiate.

Observer Pattern: let objects subscribe to events without the publisher knowing anything about who's listening.

Checkpoint: In the Observer Pattern example, does EventBus need to know anything about what its subscribers do?

  • →No — EventBus only knows it can call each subscriber with an event; the subscriber logic is fully decoupled
  • →Yes — EventBus must import and know about each subscriber's specific class

Decorator Pattern: add responsibilities to an object dynamically. Python's @decorator syntax IS a built-in implementation of this exact pattern for functions.

That completes the design-thinking layer of this module — Abstract Base Classes and Protocols next give you the formal tools these patterns are often expressed through.

Swap a Real Strategy at Runtime. Finish checkout(): a plain function already satisfies a Strategy interface in Python.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Learn the problem each pattern solves, not just its textbook Java implementation

Strategy, Factory, Observer, and Decorator all solve real, recurring design problems — but their idiomatic Python form (a function, a match statement, a callable list, @decorator) often looks nothing like the class-heavy structure most pattern references show first.

Reach for a pattern because a specific problem is present, not to look sophisticated

A three-line function doesn't need a Strategy interface wrapped around it 'for extensibility' if there's only ever going to be one implementation — apply a pattern once the actual problem (needing to swap behavior, decouple publishers/subscribers) genuinely shows up.

Frequent Bugs

THE BUG

Porting a design pattern's Java/C# textbook structure literally into Python (a full ConcreteStrategyA/ConcreteStrategyB class hierarchy for what could be two plain functions), adding unnecessary ceremony.

THE FIX

Before implementing a pattern, ask what Python language feature (first-class functions, match/case, @decorator, duck typing) might already solve the same underlying problem more simply than the pattern's original class-based form.

Real-World Examples

A Pluggable Notification System Using Strategy and Observer Together

A system needs to send notifications through swappable channels (email, SMS, push) and needs other parts of the codebase to react to "notification sent" events without coupling to the notification logic itself.

def send_email(user, message): ...
def send_sms(user, message): ...

class NotificationService:
    def __init__(self, channel, event_bus):
        self.channel = channel   # Strategy
        self.event_bus = event_bus  # Observer publisher

    def notify(self, user, message):
        self.channel(user, message)
        self.event_bus.publish(f"notified:{user}")

service = NotificationService(send_email, EventBus())

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Implementing the Strategy Pattern with a full AbstractStrategy base class and one concrete subclass per algorithm, when the algorithms are stateless and a plain function would work identically with less code.

# Unnecessary ceremony for stateless behavior class DiscountStrategy(ABC): @abstractmethod def apply(self, total): ... class PercentStrategy(DiscountStrategy): def apply(self, total): return total * 0.9 # Simpler, equally correct def apply_percent(total): return total * 0.9 def checkout(total, strategy): return strategy(total)

The Solution //

Default to a plain function parameter for stateless, swappable behavior; reach for a class-based Strategy only when a variant genuinely needs to carry its own state or configuration beyond what a closure/partial can hold.

Lesson Glossary

[01]Strategy Pattern

A design pattern for making an algorithm swappable at runtime, often just a function parameter in idiomatic Python.

Code Preview
// Strategy Pattern context

[02]Factory Pattern

A design pattern that centralizes object-creation logic behind a function or method, hiding concrete class selection from callers.

Code Preview
// Factory Pattern context

[03]Observer Pattern

A design pattern decoupling event publishers from subscribers, letting subscribers register/unregister without the publisher knowing their implementation.

Code Preview
// Observer Pattern context

[04]Decorator Pattern

A design pattern for dynamically adding responsibilities to an object without modifying its source, implemented natively via Python's @decorator syntax.

Code Preview
// Decorator Pattern context

Continue Learning