Understanding Design Patterns Through Real-World Coding Examples

When developers start building applications—be it a simple CRUD app or a complex enterprise system—they often use certain solutions or approaches without knowing they are actually following design patterns. These patterns are not rules or libraries; they are tried-and-true ways to solve recurring software problems. They bring structure, clarity, and flexibility to your code.

Let’s explore some common design patterns with real-world code scenarios that you might have already encountered.

Repository Pattern – Abstracting the Data Layer

If you’ve ever implemented Create, Read, Update, and Delete (CRUD) operations in your project, you’ve unknowingly used the Repository Pattern.

What It Does:

It separates the business logic from the data access logic, creating a clear boundary. You interact with a consistent interface rather than worrying about how the data is stored or retrieved (whether from SQL, NoSQL, an API, or an in-memory list).

Example:

class UserRepository:
    def get_by_id(self, user_id):
        # Fetch user from database
        pass
    def save(self, user):
        # Save user to database
        pass
    def delete(self, user_id):
        # Delete user from database
        pass

Even if the data source changes (like moving from SQLite to PostgreSQL), your business code doesn’t need to change—only the repository’s internal logic does.

Unit of Work Pattern – Managing Transactions

Have you ever handled a scenario where multiple database operations need to succeed together—and if one fails, everything should roll back?

What It Does:

The Unit of Work pattern treats a set of changes to your data as a single transaction. If something fails, it rolls back all changes, ensuring data integrity.

Example:

with db_session() as session:
    session.add(order)
    session.add(payment)
    session.commit()

Here, both order and payment must succeed. If payment fails, order is also rolled back. This is the essence of the Unit of Work Pattern.

Iterator Pattern – Going Through Collections

When you write loops like for item in list, you’re using the Iterator Pattern.

What It Does:

It provides a standard way to traverse a collection without exposing its internal structure.

Example:

for product in products:
    print(product.name)

Whether products is a list, a set, or even a custom data structure, the loop works the same way—thanks to the Iterator Pattern.

Singleton Pattern – Single Instance Access

Sometimes, you want to ensure that only one instance of a class is created during the entire lifetime of your application—like a shared database connection or a configuration manager.

What It Does:

It restricts the instantiation of a class to one object and provides a global point of access to it.

Example:

class DatabaseConnection:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            # Initialize the connection
        return cls._instance

No matter how many times you create DatabaseConnection, only one instance will exist.

Factory Pattern – Dynamic Object Creation

Say you want to create objects, but you don’t want to hard-code which class to instantiate. The Factory Pattern comes to the rescue.

What It Does:

It defines an interface for creating an object, but lets subclasses or conditions decide which class to instantiate.

Example:

def notification_factory(type):
    if type == "email":
        return EmailNotification()
    elif type == "sms":
        return SMSNotification()
    elif type == "push":
        return PushNotification()

You can call notification_factory(“sms”) without worrying about the class details—your logic stays clean and scalable.

Observer Pattern – Reacting to Changes

You’ve seen apps where a user updates their profile, and the change reflects in multiple areas instantly. This is where the Observer Pattern shines.

What It Does:

It defines a one-to-many relationship so when one object (subject) changes state, all its dependents (observers) are notified automatically.

Example:

class Subject:
    def __init__(self):
        self._observers = []
    def subscribe(self, observer):
        self._observers.append(observer)
    def notify(self, data):
        for obs in self._observers:
            obs.update(data)

This pattern is common in UI frameworks, real-time applications, and event-driven systems.

Strategy Pattern – Swapping Algorithms Dynamically

Imagine you have different payment methods—PayPal, Stripe, or Credit Card—and want to choose between them at runtime.

What It Does:

The Strategy Pattern allows you to define a family of algorithms and select one at runtime, promoting flexibility and clean code.

Example:

class PayPalPayment:
    def pay(self, amount):
        print(f"Paid {amount} via PayPal")
class StripePayment:
    def pay(self, amount):
        print(f"Paid {amount} via Stripe")
class PaymentProcessor:
    def __init__(self, strategy):
        self.strategy = strategy
    def process(self, amount):
        self.strategy.pay(amount)
# Usage:
processor = PaymentProcessor(PayPalPayment())
processor.process(100)

Want to switch to Stripe? Just pass a different strategy—no need to modify internal logic.

how you already solve problems and refining your solutions in a more scalable, reusable way. If you’ve built real-world projects, chances are you’ve already used these patterns without realizing it.

By understanding these patterns clearly, you can improve your code structure, communicate better with other developers, and design applications that are easier to maintain and scale.

Reach Out to me!

DISCUSS A PROJECT OR JUST WANT TO SAY HI? MY INBOX IS OPEN FOR ALL