Beyond the Boilerplate: Modern Design Patterns with Python Functions
Overview
Design patterns are often misunderstood as rigid templates meant only for strictly object-oriented languages like or . Many developers argue that the classic patterns are obsolete in because functions are first-class citizens. However, design patterns aren't about the syntax of classes; they are about identifying and solving specific categories of architectural problems. This guide explores how to implement the Strategy, Observer, and Template Method patterns using a functional approach to write cleaner, more maintainable code.
Prerequisites
To follow along, you should have a solid grasp of Python's basic syntax, including how to define functions and classes. Familiarity with and the concept of will help you understand the transition from traditional to functional implementations.
Key Libraries & Tools
- ABC (Abstract Base Classes): A module used to define blueprints for classes to ensure subclasses implement specific methods.
- Functools: A built-in library for higher-order functions; we specifically use
partialfor function application. - Callable: A type hint from the
typingmodule used to define the signature of a function being passed as an argument.
Code Walkthrough: Strategy Pattern
The allows you to swap algorithms at runtime. In the traditional approach, you create an abstract base class and inherit from it for every new algorithm. In Python, you can replace this entire hierarchy with a simple function signature.

from typing import Callable, List
SortFunction = Callable[[List[int]], List[int]]
def bubble_sort(data: List[int]) -> List[int]:
# Implementation logic
return sorted(data)
def quick_sort(data: List[int]) -> List[int]:
# Implementation logic
return sorted(data)
def process_data(data: List[int], strategy: SortFunction):
# Perform operations
result = strategy(data)
print(f"Result: {result}")
# Usage
process_data([5, 2, 9], bubble_sort)
By passing the function directly, you eliminate the need for a "Context" class and multiple inheritance layers. This reduces 65 lines of boilerplate to roughly 50, making the code much easier to navigate.
Code Walkthrough: Observer Pattern
The decouples the subject (the thing that changes) from its observers (the things that react). Instead of maintaining a list of objects with an update() method, maintain a list of callables.
Observer = Callable[[str], None]
def notify(observers: List[Observer], message: str):
for observer in observers:
observer(message)
def email_logger(msg: str): print(f"Email: {msg}")
def db_logger(msg: str): print(f"DB: {msg}")
notify([email_logger, db_logger], "Payment Successful")
This functional approach keeps the core logic pure. You aren't forcing your loggers to inherit from a specific interface; they just need to match the expected function signature.
Syntax Notes: Closures and Partial Application
When moving to functional patterns, you'll encounter the . While classes use method overriding, functional Python uses Closures and Partial Application.
Using functools.partial allows you to pre-fill arguments of a function, effectively creating a "specialized" version of a general template. It’s a cleaner alternative to nesting functions within functions (closures), though both achieve the goal of fixing certain parts of an algorithm while leaving others flexible.
Practical Examples
These techniques are highly effective in modern web development. For instance, in an gateway, you might use the Strategy pattern to handle different authentication methods (JWT, OAuth, API Key) by simply passing different validation functions into your main handler. Similarly, the Observer pattern is the backbone of event-driven architectures where services need to react to user actions without being tightly coupled to the action-triggering logic.
Tips & Gotchas
- Readability: While functional patterns are concise, deeply nested closures can be difficult to debug. Use
partialwhen possible to keep the flat structure. - Signatures: Always use Type Hints with
Callable. Without them, it’s hard to tell what arguments the "strategy" function requires just by looking at the variable name. - Object State: If your strategy or observer needs to maintain complex internal state across multiple calls, a class might still be the better tool for the job. Don't force a functional solution if it makes state management a nightmare.
- 10%· concepts
- 10%· concepts
- 10%· languages
- 10%· books
- 10%· languages
- Other topics
- 50%

Why Use Design Patterns When Python Has Functions?
WatchArjanCodes // 23:23
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!