AiTechWorlds
AiTechWorlds
When Netflix decides a video file has finished encoding, dozens of services need to know: the metadata service must index it, the recommendation engine must consider it, the CDN must pre-cache it, and the notification service may need to alert subscribers.
Rewriting the encoding service to call each downstream service directly would create a maintenance nightmare — every new service requires changing the encoder, and the encoder must know about recommendation engines and CDN cache APIs.
Netflix solved this with the Observer pattern: the encoder publishes an "encoding complete" event, and any interested service subscribes to it. The encoder knows nothing about its subscribers. New services join without touching the encoder.
When they needed to layer analytics, throttling, and authentication onto their API gateway without rewriting the core service logic, they used the Decorator pattern.
Understanding these patterns means understanding how Netflix, Amazon, and Google build systems that evolve without collapsing under their own weight.
Structural patterns describe how objects and classes are composed to form larger structures while keeping those structures flexible and efficient.
Problem: You have an existing class with a useful interface, and a client that expects a different interface. They are incompatible but you cannot modify either.
Analogy: A European power plug and a US socket. The plug and socket are incompatible. An adapter converts one to the other without modifying either.
class EuropeanPlug: # Existing class (third-party, cannot modify)
def provide_220v(self):
return "220V power"
class USSocket: # Client expects this interface
def use_110v(self): ...
class PowerAdapter(USSocket): # Adapter bridges the gap
def __init__(self, plug: EuropeanPlug):
self.plug = plug
def use_110v(self):
raw = self.plug.provide_220v()
return f"Converted: {raw} → 110V"
# Client code unchanged — uses USSocket interface
adapter = PowerAdapter(EuropeanPlug())
print(adapter.use_110v()) # Converted: 220V power → 110V
Real uses: Python's io.StringIO wrapping raw bytes for text-based APIs, Java JDBC drivers adapting vendor-specific database protocols to a standard interface, payment gateway adapters normalising Stripe and PayPal APIs to a single PaymentProvider interface.
Problem: You need to add behaviour to an object dynamically, at runtime, without modifying the class or affecting other objects of the same class.
Analogy: Gift wrapping. A book is a book. Wrapping it adds a bow. Wrapping again adds a tag. Each wrapper adds behaviour without changing the book.
from functools import wraps
import time
def timer(func): # Decorator: adds timing behaviour
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs) # Calls original function
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def cache(func): # Decorator: adds caching behaviour
store = {}
@wraps(func)
def wrapper(*args):
if args not in store:
store[args] = func(*args)
return store[args]
return wrapper
@timer
@cache
def compute_fibonacci(n):
if n <= 1: return n
return compute_fibonacci(n - 1) + compute_fibonacci(n - 2)
compute_fibonacci(30) # First call: slow + timed. Second call: instant + timed.
Real uses: Python's @functools.lru_cache (caching), @login_required in Django (authentication), @property (controlled attribute access), logging middleware in web frameworks, compression layers in I/O streams.
Problem: A subsystem is complex — many classes, dependencies, initialisation sequences. Clients should not need to understand all of it.
Solution: A Facade provides a simplified interface to the complex subsystem.
class VideoDecoder:
def decode(self, file): return f"Decoded {file}"
class AudioDecoder:
def decode(self, file): return f"Audio from {file}"
class SubtitleParser:
def parse(self, file): return f"Subtitles from {file}"
class VideoPlayerFacade: # Simple interface to complex subsystem
def __init__(self):
self._video = VideoDecoder()
self._audio = AudioDecoder()
self._subtitles = SubtitleParser()
def play(self, file):
print(self._video.decode(file)) # Coordinates multiple subsystems
print(self._audio.decode(file))
print(self._subtitles.parse(file))
print("Playing...")
player = VideoPlayerFacade()
player.play("movie.mp4") # Client calls one method
Real uses: Python's requests library (facades over urllib3, SSL, cookies), jQuery's .ajax() hiding XMLHttpRequest complexity, cloud SDK clients hiding dozens of lower-level API calls.
Problem: You want to control access to an object — for lazy initialisation, access control, logging, or remote access.
class RealDatabaseQuery:
def execute(self, sql):
print(f"[DB] Executing: {sql}") # Expensive operation
return ["row1", "row2"]
class CachingProxy:
def __init__(self):
self._real = RealDatabaseQuery()
self._cache = {}
def execute(self, sql):
if sql not in self._cache:
self._cache[sql] = self._real.execute(sql)
print("[Cache] Miss — queried DB")
else:
print("[Cache] Hit — returned from cache")
return self._cache[sql]
db = CachingProxy()
db.execute("SELECT * FROM users") # [DB] Executing... [Cache] Miss
db.execute("SELECT * FROM users") # [Cache] Hit — no DB call
Real uses: Lazy-loading ORMs (Django only queries the DB when you iterate a QuerySet), virtual proxies for expensive objects, protection proxies for access control, remote proxies (gRPC stubs).
Problem: You need to treat individual objects and compositions of objects uniformly. Files and folders should both respond to get_size().
Real uses: File system tree traversal, UI component hierarchies (React's component tree), HTML DOM, organisation chart traversal.
Behavioural patterns describe communication and responsibility between objects.
Problem: When one object changes, many others need to be notified — without tight coupling between them.
class EventBus:
def __init__(self):
self._subscribers = {}
def subscribe(self, event_type, callback):
self._subscribers.setdefault(event_type, []).append(callback)
def publish(self, event_type, data=None):
for callback in self._subscribers.get(event_type, []):
callback(data)
bus = EventBus()
# Services subscribe to events they care about
bus.subscribe("video.ready", lambda d: print(f"MetadataService: indexing {d}"))
bus.subscribe("video.ready", lambda d: print(f"CDNService: pre-caching {d}"))
bus.subscribe("video.ready", lambda d: print(f"NotificationService: alerting for {d}"))
# Encoder publishes — knows nothing about subscribers
bus.publish("video.ready", "movie_1234.mp4")
Real uses: JavaScript's addEventListener, React's useEffect dependency watching, Python's signal module, pub/sub message brokers (AWS SNS, Google Pub/Sub, Kafka).
Problem: An algorithm can be swapped at runtime. Hardcoding which algorithm to use makes the code rigid.
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float): ...
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Charged ${amount:.2f} to credit card")
class PayPalPayment(PaymentStrategy):
def pay(self, amount):
print(f"Sent ${amount:.2f} via PayPal")
class CryptoPayment(PaymentStrategy):
def pay(self, amount):
print(f"Transferred ${amount:.2f} in crypto")
class ShoppingCart:
def __init__(self, strategy: PaymentStrategy):
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy):
self._strategy = strategy # Swap at runtime
def checkout(self, total):
self._strategy.pay(total)
cart = ShoppingCart(CreditCardPayment())
cart.checkout(99.99) # Charged $99.99 to credit card
cart.set_strategy(CryptoPayment())
cart.checkout(49.50) # Transferred $49.50 in crypto
Real uses: Payment processors (Stripe, PayPal, Apple Pay), sorting algorithms in language standard libraries, compression algorithms (gzip, zstd, brotli), authentication strategies (OAuth, SAML, password-based).
Problem: Encapsulate a request as an object so it can be queued, logged, undone, or sent over a network.
Real uses: Text editor undo/redo stacks, job queues (Celery tasks, AWS SQS messages), database transaction logs, macro recording.
Problem: An algorithm has a fixed structure, but certain steps must be customisable by subclasses.
class DataProcessor(ABC):
def process(self): # Template method — fixed structure
raw = self.read() # Step 1: read (abstract)
data = self.transform(raw) # Step 2: transform (abstract)
self.write(data) # Step 3: write (abstract)
@abstractmethod
def read(self): ...
@abstractmethod
def transform(self, data): ...
@abstractmethod
def write(self, data): ...
Real uses: Django class-based views (get(), post() override specific steps), unit test setUp()/tearDown(), HTTP request lifecycle hooks.
Problem: Traverse a collection without exposing its internal representation.
Python's for x in collection uses the iterator protocol (__iter__, __next__) — the Iterator pattern built into the language. This is why you can iterate lists, dictionaries, generators, files, and database QuerySets with the same for loop.
| Pattern | Category | Problem | Solution | Real Use Case |
|---|---|---|---|---|
| Adapter | Structural | Incompatible interfaces | Wrapper translates interface | JDBC drivers, payment gateway adapters |
| Decorator | Structural | Add behaviour dynamically | Chain wrappers | Python decorators, logging middleware |
| Facade | Structural | Complex subsystem | Simple unified interface | requests library, jQuery .ajax() |
| Proxy | Structural | Control object access | Placeholder with same interface | Django QuerySet lazy loading, CDN |
| Composite | Structural | Tree structures | Uniform treatment of leaf and branch | File system, React component tree |
| Observer | Behavioural | One-to-many notification | Publish/subscribe | JavaScript events, Kafka, React hooks |
| Strategy | Behavioural | Interchangeable algorithms | Encapsulate each algorithm | Payment methods, sort algorithms |
| Command | Behavioural | Encapsulate requests | Request as object | Undo/redo, job queues |
| Template Method | Behavioural | Fixed structure, custom steps | Abstract steps in base class | Django views, test lifecycle |
| Iterator | Behavioural | Traverse without exposing | Iterator protocol | Python for loop, database cursors |
@decorator syntax is the Decorator pattern built into the language.Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises