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.
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:
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
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 with no children or a containing fifty nested components — exposes the same interface: accept props, return elements. React traverses the tree recursively, treating each node identically.
The DOM itself:
// Pseudo code — the browser DOM is a classic Composite
document.querySelector("div") // Could be leaf or branch
.appendChild(newChild) // Same API for any element
.addEventListener("click", handler) // Same API for any element
.children // Might be empty, might be hundreds
// A <span> and a <div> with 50 nested children
// both respond to the same methods
The revelation: Every JSX tree you've written is a Composite. The reason you can nest inside inside and it "just works" is because React implemented Composite as its core rendering model.
Pattern 5: Strategy — Swappable Behavior
The ELI5
You're at a restaurant and you can pay with cash, card, or mobile. The cashier doesn't care which you choose — they just say "that'll be $20" and you pick your payment strategy. The bill is the same; only the method of payment changes. Strategy lets you swap algorithms or behaviors without changing the code that uses them.
Where You've Seen It
React's render props and children-as-functions:
// Pseudo code — React render props ARE the Strategy pattern
COMPONENT DataFetcher(url, renderStrategy):
data = fetchFrom(url)
RENDER:
renderStrategy(data) // Caller decides HOW to render
// Usage — same component, different strategies:
<DataFetcher url="/users" renderStrategy={(data) =>
<UserTable rows={data} /> // Strategy A: render as table
}/>
<DataFetcher url="/users" renderStrategy={(data) =>
<UserCards items={data} /> // Strategy B: render as cards
}/>
// Pseudo code — Spring's JdbcTemplate uses Strategy for row mapping
jdbcTemplate.query(
"SELECT * FROM users WHERE active = true",
rowMapper // YOU provide the strategy
)
// The strategy decides how to transform each database row:
FUNCTION userRowMapper(row):
RETURN new User(
name: row.getString("name"),
email: row.getString("email")
)
// Same query infrastructure, different mapping strategies
FUNCTION adminRowMapper(row):
RETURN new AdminUser(
name: row.getString("name"),
permissions: row.getArray("permissions")
)
Django's storage backends:
// Pseudo code — Django lets you swap file storage strategies
// In settings:
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
// OR
DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
// Your code NEVER changes:
model.image.save("photo.jpg", file) // Same API regardless of strategy
// Django routes to S3 or local disk based on which strategy is configured
The revelation: Render props in React, RowMapper callbacks in Spring, and pluggable storage backends in Django are all Strategy. Any time a framework lets you "plug in" your own behavior while it handles the scaffolding, you're looking at Strategy.
A bodyguard stands between a celebrity and the public. Fans think they're interacting with the celebrity's "camp," but the bodyguard is actually screening requests — blocking paparazzi, scheduling meet-and-greets, and only letting approved people through. The bodyguard looks like the celebrity's representative but adds a layer of control. That's Proxy: an object that stands in for another object and controls access to it.
Where You've Seen It
Spring's AOP (Aspect-Oriented Programming):
// Pseudo code — Spring creates proxy objects behind the scenes
@Service
CLASS PaymentService:
@Transactional // This annotation triggers a PROXY
METHOD processPayment(order):
debit(order.customer, order.total)
credit(merchant, order.total)
// What Spring ACTUALLY does at runtime:
CLASS PaymentServiceProxy WRAPS PaymentService:
METHOD processPayment(order):
BEGIN transaction // Proxy adds behavior BEFORE
TRY:
actual.processPayment(order) // Delegate to real object
COMMIT transaction // Proxy adds behavior AFTER
CATCH error:
ROLLBACK transaction // Proxy handles errors
THROW error
Every time you add @Transactional, @Cacheable, or @Secured to a Spring method, Spring wraps your class in a Proxy. Your code thinks it's calling PaymentService directly, but it's actually calling the proxy, which adds transactions, caching, or security checks invisibly.
JavaScript's built-in Proxy object (used by Vue 3 and MobX):
// Pseudo code — Vue 3's reactivity is built on JavaScript Proxy
state = reactive({ count: 0 }) // Vue wraps this in a Proxy
// When you read state.count:
// → The Proxy intercepts the read
// → Tracks which component is reading (dependency tracking)
// When you write state.count = 5:
// → The Proxy intercepts the write
// → Notifies all tracked components to re-render
// You interact with 'state' normally — the Proxy is invisible
state.count = state.count + 1 // Feels normal, triggers re-render
The revelation: Spring annotations like @Transactional and @Cacheable work by wrapping your objects in Proxies. Vue 3's entire reactivity system is literally a JavaScript Proxy. Any time a framework magically "intercepts" your method calls to add behavior, there's a Proxy behind the curtain.
The Cross-Framework Pattern Map
Here's a cheat sheet showing where each pattern appears across the frameworks we explored:
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:
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.
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.
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.'