Patternsintermediateschedule15 min read

Design Patterns in the Real World: Examples from Popular Frameworks

You've been using design patterns every day without realizing it. Take a 'Pattern Safari' through React, Express, Django, Spring, Angular, and Redux to discover the GoF patterns hiding in plain sight inside the frameworks you already know.

a black and white photo of a pattern on a wall

You're Already on a Pattern Safari — You Just Don't Have Binoculars

Here's a thought experiment. Open up your last project and count how many of these lines you've written:

app.use(authMiddleware)
useState(initialValue)
@Injectable()
store.dispatch(action)

Every single one of those is a design pattern in action. Not a textbook exercise. Not an academic toy example. A real pattern, baked into a framework you use every day, solving a problem that thousands of developers hit before you.

The Gang of Four published their 23 design patterns in 1994. Thirty years later, those same patterns are the invisible architecture inside React, Express, Django, Spring, Angular, and Redux. The framework authors didn't reinvent the wheel — they reached for proven solutions and wrapped them in friendly APIs.

This article is a safari. We're going to grab our binoculars and spot six classic patterns hiding inside frameworks you already know. For each one, you'll get the one-paragraph explanation, the framework code where it lives, and the "wait, I've been using this the whole time" moment.


Pattern 1: Observer — The Pattern That Powers Every UI Framework

The ELI5

Imagine a newspaper subscription. You sign up, and the newspaper delivers to your door every morning. You don't call the newspaper every day asking "is there a new edition?" — they push it to you. If you cancel, they stop delivering. That's the Observer pattern: one thing changes, and every subscriber gets notified automatically.

Where You've Seen It

React's useState:

// Pseudo code — what happens inside React's useState
COMPONENT ShoppingCart:
    [items, setItems] = useState([])      // Subscribe to 'items' state

    FUNCTION addItem(product):
        setItems(items + product)          // State changes...
        // React AUTOMATICALLY re-renders this component
        // and every child that depends on 'items'
        // You never call 'pleaseReRender()' — it just happens

When you call setItems, React's internal Observer mechanism kicks in. Your component subscribed to that state when it called useState. State changed → subscriber notified → UI updated. You never wrote an "observer" class, but you used the pattern every time you called a setter.

Angular's RxJS Observables:

// Pseudo code — Angular's Observable pattern
SERVICE DataService:
    users$ = new Observable()              // The 'newspaper'

    METHOD fetchUsers():
        GET data from API
        users$.emit(data)                  // Publish new edition

COMPONENT UserList:
    ON_INIT:
        dataService.users$.subscribe(      // Subscribe!
            (users) => this.users = users   // Automatically called on new data
        )

Angular went all-in on Observer by making RxJS observables a core dependency. Every HTTP request, every form change, every route transition — all observables. All Observer pattern.

Redux's store.subscribe:

// Pseudo code — Redux store is a classic Observable
store = createStore(reducer)

store.subscribe(() => {                    // I want to know when state changes
    newState = store.getState()
    updateUI(newState)                     // React-Redux does this behind the scenes
})

store.dispatch({ type: "ADD_ITEM" })       // State changes → all subscribers notified
The revelation: Every time you've used useState, subscribed to an Observable, or connected a component to a Redux store, you were using the Observer pattern. The framework just hid the subscription plumbing.

Pattern 2: Chain of Responsibility — The Middleware Pipeline

The ELI5

Picture an airport security line. First, someone checks your boarding pass. Then someone scans your bag. Then someone checks for liquids. Each station has one job and either handles your issue or passes you to the next station. Any station can pull you out of the line entirely ("sorry, you can't bring that sword on the plane"). That's Chain of Responsibility: a request passes through a series of handlers, each one deciding to process it, modify it, or pass it along.

Where You've Seen It

Express.js middleware:

// Pseudo code — Express middleware IS Chain of Responsibility
app.use(logRequest)         // Station 1: Log every request
app.use(authenticate)       // Station 2: Check auth token
app.use(rateLimit)          // Station 3: Block if too many requests
app.use(parseBody)          // Station 4: Parse JSON body
app.use(handleRoute)        // Station 5: Actually do the thing

// Each middleware function looks like this:
FUNCTION authenticate(request, response, next):
    IF request HAS valid token:
        next()               // Pass to next handler in chain
    ELSE:
        response.send(401)   // Short-circuit — stop the chain here

Every app.use() adds a link to the chain. Each middleware can modify the request, send a response (ending the chain), or call next() to pass control downstream.

Django's middleware pipeline:

// Pseudo code — Django middleware is the same pattern in Python
MIDDLEWARE_STACK = [
    SecurityMiddleware,       // Add security headers
    SessionMiddleware,        // Load session data
    AuthenticationMiddleware, // Attach user to request
    CsrfViewMiddleware,       // Check CSRF token
    YourCustomMiddleware,     // Your station in the chain
]

// Each middleware:
CLASS AuthenticationMiddleware:
    METHOD process_request(request):
        user = lookup_user_from_session(request)
        request.user = user    // Modify request, pass it along
        RETURN none            // None = continue chain
        // OR return a Response to short-circuit

Redux middleware:

// Pseudo code — Redux middleware pipeline
// Actions flow through middleware before reaching the reducer

FUNCTION loggingMiddleware(store)(next)(action):
    PRINT "Dispatching:", action       // Do something with the action
    result = next(action)              // Pass to next middleware
    PRINT "New state:", store.getState()
    RETURN result

FUNCTION thunkMiddleware(store)(next)(action):
    IF action IS a function:
        action(store.dispatch)          // Handle async — DON'T pass to next
    ELSE:
        next(action)                    // Normal action — pass it along

The following diagram shows how a request flows through an Express middleware chain — the same flow applies conceptually to Django and Redux:

hourglass_empty

Rendering diagram...

The revelation: app.use() in Express, MIDDLEWARE in Django's settings, and applyMiddleware() in Redux are all the same pattern. If you've chained middleware in any one of them, you can instantly reason about the others.

Pattern 3: Singleton — One Instance to Rule Them All

A scrabble block spelling out the word pattern

Photo by Markus Winkler on Unsplash

The ELI5

Your house has one thermostat. Everyone in the family adjusts the same thermostat — you don't install a separate one in every room that each controls its own furnace. One shared instance, accessible from anywhere. That's Singleton.

Where You've Seen It

Angular's @Injectable services:

// Pseudo code — Angular services are singletons by default
@Injectable({ providedIn: 'root' })    // 'root' = one instance for entire app
CLASS UserService:
    PRIVATE currentUser = null

    METHOD login(credentials):
        this.currentUser = authenticate(credentials)

    METHOD getUser():
        RETURN this.currentUser

// Component A and Component B both inject UserService
// They get the EXACT SAME instance — same currentUser, same state

Angular's dependency injection container creates one UserService when any component first asks for it, then hands that same instance to every subsequent requestor. You didn't write getInstance() — the framework's DI container is the Singleton enforcer.

Spring's bean container:

// Pseudo code — Spring beans are singletons by default
@Component                              // Spring creates ONE instance
CLASS PaymentGateway:
    METHOD charge(amount):
        CALL external payment API

@Component
CLASS OrderService:
    @Autowired
    PRIVATE PaymentGateway gateway      // Same instance everywhere

@Component
CLASS RefundService:
    @Autowired
    PRIVATE PaymentGateway gateway      // Same instance as OrderService got

Spring's entire bean system defaults to singleton scope. Every @Component, @Service, and @Repository produces exactly one instance per application context.

The revelation: If you've ever used Angular's providedIn: 'root' or Spring's @Component, you've relied on the Singleton pattern. The framework's dependency injection IS the Singleton — you just never had to write the boilerplate.

Pattern 4: Composite — Trees All the Way Down

The ELI5

Think of a file system. A folder can contain files and other folders. You can ask a folder "how big are you?" and it sums up everything inside — including nested folders that sum up their contents. Individual files and folders of files both respond to the same question the same way. That's Composite: treating individual objects and groups of objects uniformly.

Where You've Seen It

React's component tree:

// Pseudo code — React IS a Composite tree
COMPONENT App:                          // A composite node
    RENDER:
        <Layout>                        // Another composite
            <Header />                  // Leaf node
            <Sidebar>                   // Composite — contains children
                <NavLink />             // Leaf
                <NavLink />             // Leaf
                <NavGroup>              // Composite inside composite
                    <NavLink />         // Leaf
                </NavGroup>
            </Sidebar>
            <MainContent />            // Leaf
        </Layout>

// React treats EVERY node the same way:
// - Call render() on it
// - Pass props down to it
// - Reconcile it in the virtual DOM
// It doesn't care if a component has children or not.

React's entire rendering model is Composite. Every component — whether it's a simple

hourglass_empty

Rendering diagram...

Notice how the same patterns recur across completely different ecosystems. That's not coincidence — it's because these patterns solve universal problems. Request pipelines need Chain of Responsibility whether you're in Python or JavaScript. Reactive UIs need Observer whether you're in React or Angular.


Why This Matters For Your Career

Knowing the patterns behind your frameworks gives you three concrete advantages:

  1. You debug faster. When Express middleware doesn't fire, you know to check the chain order — because you understand it's Chain of Responsibility, and link order matters. When a Spring @Transactional doesn't work on a private method, you know why — because Proxies can only intercept calls that go through the proxy, not internal calls.
  1. You learn new frameworks in hours, not weeks. Once you've seen Chain of Responsibility in Express, you recognize it instantly in Django, ASP.NET, Koa, and Hono. The syntax changes; the shape doesn't.
  1. You make better design decisions. When you need to add a cross-cutting concern to your own application, you don't reinvent the wheel — you recognize "this is a Chain of Responsibility problem" and structure your code accordingly.

When NOT to Go Pattern Hunting

A word of caution: don't let this article turn you into a pattern-obsessed architecture astronaut. Just because you can name the pattern inside a framework doesn't mean you need to replicate the full formal structure in your application code.

  • Don't build your own Observer system when useState or RxJS already exists.
  • Don't implement Chain of Responsibility from scratch when app.use() is right there.
  • Don't wrap everything in Proxy classes when a simple function call does the job.

The point of recognizing patterns in frameworks is to understand your tools better — not to add layers of abstraction on top of abstractions that already exist.

Martin Fowler put it best: patterns are "half-baked solutions" — they give you a starting shape, but the framework has already finished baking it for you. Use the framework's implementation. Save the pattern knowledge for when you're designing something new or debugging something broken.


Key Takeaways

  • You've been using GoF patterns every day. useState is Observer. app.use() is Chain of Responsibility. @Injectable is Singleton. JSX trees are Composite. Render props are Strategy. @Transactional is Proxy.
  • The same patterns appear across completely different frameworks because they solve universal structural problems. Learning the pattern once gives you insight into every framework that uses it.
  • Pattern literacy is a debugging superpower. Understanding why a framework is structured a certain way helps you predict its behavior and diagnose its failures.
  • Don't reinvent what frameworks already provide. Recognize the patterns to understand your tools — then use those tools instead of building your own.

Frequently Asked Questions

helpDo framework developers intentionally use GoF design patterns, or do patterns just emerge naturally?

Both. Some frameworks deliberately adopt named patterns — Spring's documentation explicitly references Singleton, Factory, and Proxy. Others arrive at the same structures independently because the patterns solve universal problems. Django's middleware pipeline wasn't designed by someone saying 'let's implement Chain of Responsibility' — it was designed to let requests pass through a sequence of handlers, and that IS the Chain of Responsibility. The pattern is the name we give to the shape, whether or not the creator had the name in mind.

helpIf I learn the patterns inside one framework, will I recognize them in other frameworks too?

Absolutely — that's the biggest payoff of pattern literacy. Once you understand that Express app.use() is Chain of Responsibility, you'll instantly recognize the same structure in Django middleware, Redux middleware, and ASP.NET's pipeline. The syntax changes, but the shape stays the same. This is why patterns are called a 'shared vocabulary' — they transcend languages and frameworks.

helpAre there design patterns that appear in almost every modern framework?

Yes. Observer, Singleton, and Chain of Responsibility are nearly universal. Observer appears in any framework with event handling or reactive state (React, Angular, Vue, Redux). Singleton shows up in any framework with a service container or shared configuration. Chain of Responsibility appears in any framework with a middleware or plugin pipeline. If you only learn three patterns, those three will give you the most cross-framework recognition.

helpDo I need to understand these patterns to use these frameworks effectively?

You can use frameworks without knowing the underlying patterns — millions of developers do. But understanding them gives you two superpowers: you debug faster because you understand WHY the framework behaves the way it does, and you make better architectural decisions because you recognize when to follow the framework's patterns versus when to fight them. It's the difference between driving a car and understanding how the engine works — both get you there, but one helps you diagnose the weird noise.

helpHave any GoF patterns become obsolete in modern frameworks?

Not obsolete, but several have been absorbed so deeply into languages and frameworks that you rarely implement them from scratch. Iterator is built into every language with for-each loops. Observer is baked into React hooks, Angular RxJS, and browser event listeners. Singleton is just a module-scoped variable in JavaScript. The patterns aren't gone — they've graduated from 'thing you build yourself' to 'thing the platform gives you for free.'