• Backend Engineering
  • 🧠 Software Design Patterns: A Practical Guide for Engineers

    Design patterns are not academic theory… they are practical tools that help you write code that is:

    • Easier to maintain 🧹
    • Easier to scale πŸš€
    • Easier to understand πŸ‘€

    πŸ‘‰ As a Software Engineer, mastering design patterns is what separates:

    writing code that works βœ… vs designing systems that last πŸ’₯


    πŸ“Œ What Are Design Patterns?

    Design patterns are reusable solutions to common problems in software design.

    πŸ’‘ Important:

    They are NOT copy-paste code β†’ they are ways of thinking and structuring solutions


    🧩 Types of Design Patterns

    There are 3 main categories:

    πŸ—οΈ Creational

    How objects are created
    πŸ‘‰ Example: Singleton, Factory

    πŸ”— Structural

    How objects are composed
    πŸ‘‰ Example: Adapter, Decorator

    βš™οΈ Behavioral

    How objects interact
    πŸ‘‰ Example: Observer, Strategy


    πŸ”₯ Most Common Design Patterns (With Real Examples)

    Let’s focus on the ones you’ll actually use πŸ‘‡


    1️⃣ Singleton πŸ§β€β™‚οΈ

    πŸ“Œ What It Is

    Ensures that a class has only one instance.


    🧠 When to Use It

    • Database connections
    • Configuration
    • Logging

    πŸ’» Example (Go)

    var instance *DB
    var once sync.Once
    
    func GetDB() *DB {
        once.Do(func() {
            instance = &DB{}
        })
        return instance
    }
    

    ⚠️ Common Problem

    • Harder to test
    • Introduces global state

    πŸ‘‰ Use it carefully


    2️⃣ Factory 🏭

    πŸ“Œ What It Is

    Encapsulates object creation logic.


    🧠 When to Use It

    When you need to create different types of objects based on conditions.


    πŸ’» Example

    type Payment interface {
        Pay(amount float64)
    }
    
    type CreditCard struct{}
    func (c CreditCard) Pay(amount float64) {}
    
    type PayPal struct{}
    func (p PayPal) Pay(amount float64) {}
    
    func PaymentFactory(method string) Payment {
        switch method {
        case "card":
            return CreditCard{}
        case "paypal":
            return PayPal{}
        default:
            return nil
        }
    }
    

    πŸ’‘ Benefits

    • Avoids scattered if/else logic
    • Centralizes object creation

    3️⃣ Strategy 🎯

    πŸ“Œ What It Is

    Allows you to switch behavior dynamically.


    🧠 When to Use It

    When you have multiple ways to perform an operation.


    πŸ’» Example

    type DiscountStrategy interface {
        Apply(price float64) float64
    }
    
    type NoDiscount struct{}
    func (d NoDiscount) Apply(price float64) float64 {
        return price
    }
    
    type TenPercent struct{}
    func (d TenPercent) Apply(price float64) float64 {
        return price * 0.9
    }
    

    Usage:

    func Calculate(price float64, strategy DiscountStrategy) float64 {
        return strategy.Apply(price)
    }
    

    πŸ’‘ Benefits

    • Flexible
    • Easy to extend
    • Clean separation of logic

    4️⃣ Observer πŸ‘€

    πŸ“Œ What It Is

    Allows multiple objects to react to an event.


    🧠 When to Use It

    • Event-driven systems
    • Notifications
    • Messaging systems

    πŸ’» Example

    type Observer interface {
        Update(data string)
    }
    
    type EmailService struct{}
    func (e EmailService) Update(data string) {}
    
    type EventManager struct {
        observers []Observer
    }
    
    func (em *EventManager) Notify(data string) {
        for _, o := range em.observers {
            o.Update(data)
        }
    }
    

    πŸ’‘ Benefits

    • Low coupling
    • Scalable event handling

    5️⃣ Decorator 🎁

    πŸ“Œ What It Is

    Adds behavior to objects without modifying them.


    🧠 When to Use It

    • Middleware
    • Logging
    • Metrics

    πŸ’» Example

    type Handler func()
    
    func LoggingMiddleware(next Handler) Handler {
        return func() {
            fmt.Println("Start")
            next()
            fmt.Println("End")
        }
    }
    

    πŸ’‘ Real Use Case

    πŸ‘‰ Middleware in APIs (very common in Go, Node.js, etc.)


    6️⃣ Adapter πŸ”Œ

    πŸ“Œ What It Is

    Allows incompatible interfaces to work together.


    🧠 When to Use It

    • Integrating third-party APIs
    • Legacy system migration

    πŸ’» Example

    type OldService struct{}
    
    func (o OldService) GetData() string {
        return "data"
    }
    
    type NewInterface interface {
        Fetch() string
    }
    
    type Adapter struct {
        old OldService
    }
    
    func (a Adapter) Fetch() string {
        return a.old.GetData()
    }
    

    πŸ’‘ Benefits

    • Keeps existing code intact
    • Simplifies integrations

    🧠 How to Use These Patterns in Real Life


    🚫 Common Mistake

    Trying to use patterns everywhere:

    ❌ Overengineering
    ❌ Unnecessary complexity
    ❌ Hard-to-maintain code


    βœ… Better Approach

    Use patterns when:

    • You see duplication
    • Code is growing in complexity
    • You need flexibility

    ⚑ Real Backend Example

    Let’s say you’re building a payment system:

    πŸ‘‰ You can combine:

    • Factory β†’ Create payment method
    • Strategy β†’ Apply pricing/discount logic
    • Observer β†’ Send notifications

    πŸ’₯ Result:
    A flexible and scalable system


    🧠 Pro Tips (Senior-Level Thinking)


    🎯 1. Focus on Problems, Not Patterns

    Good:

    β€œI have this problem β†’ which pattern solves it?”

    Bad:

    β€œI want to use this pattern no matter what”


    πŸ”„ 2. Refactor Into Patterns

    • Start simple
    • Apply patterns when needed

    🧩 3. Combine Patterns

    Real systems use multiple patterns together.


    πŸ“‰ 4. Keep It Simple

    A strong engineer:

    • Uses fewer patterns
    • But uses them correctly

    πŸš€ Final Thoughts

    Design patterns are:

    🧠 Mental models
    βš™οΈ Proven solutions
    πŸš€ Key to building scalable systems


    πŸ’₯ The Real Difference

    A junior developer:

    writes code that works

    A senior engineer:

    designs systems that evolve

    4 mins