DEV Community

Cover image for When Modern Frameworks Fail, These 6 Patterns Save the Day
John Munn
John Munn

Posted on

When Modern Frameworks Fail, These 6 Patterns Save the Day

We all like to think we’ve moved beyond design patterns. That today’s tools, frameworks, hooks, composables-have made them obsolete. But then something breaks. Badly. Not because the tool is wrong, but because the glue between things is missing. And when that happens, it’s often one of these quietly reliable patterns that steps in, not because they’re clever, but because they’re stable.

That’s what this piece is about.

You’ve heard of the Singleton. Factory, too. Maybe even Adapter if you paid attention that week. But let’s be honest. Most of the “Gang of Four” design patterns got filed away in the same mental drawer as Trigonometry and rotary phones: important once, now mostly decorative.

But here’s the thing: some of those dusty old patterns? They’re quietly propping up modern software in ways most of us don’t notice. They don’t show up in conference talks or influencer threads, but they’re there, tucked beneath the frameworks, holding the line when abstraction falters.

This isn’t a nostalgia tour. These are six patterns I’ve actually reached for in 2025. Not because I was trying to be clever, but because the code needed a backbone.


🧱 Wait—Why Patterns at All?

Design patterns aren’t magic. They’re shorthand for solutions that have stood the test of time. They give you:

  • Shared language – So your team doesn’t invent six different ways to do the same thing.
  • Mental models – So you can reason about structure, not just syntax.
  • Stability under stress – When frameworks fall short, patterns give your code something solid to fall back on.

You don’t need to memorize them. But knowing when to reach for one? That’s pro-level thinking.

These are six patterns I’ve actually reached for in 2025. Not because I was trying to be clever, but because the code needed a backbone.


🌀 Memento

When you're working on collaborative tools, undo/redo isn't a feature. It's a lifeline. Especially when you realize users expect Google Docs level history fidelity out of a weekend hackathon project.

const history = [];
function save(component) {
  history.push(structuredClone(component));
}
function undo(component) {
  if (history.length) Object.assign(component, history.pop());
}
Enter fullscreen mode Exit fullscreen mode

You don’t need Redux. You need a snapshot. Memento pattern quietly captures state so you can step backward without stepping off a cliff.


🎭 Mediator

When your component tree starts looking like a telephone switchboard—props flowing in, callbacks spilling out, and event emitters whispering secrets, it's time to introduce a Mediator. Instead of tightly coupling components, you centralize communication through a shared dispatcher.

class Mediator {
  events = {};
  subscribe(event, fn) {
    (this.events[event] ||= []).push(fn);
  }
  publish(event, data) {
    (this.events[event] || []).forEach(fn => fn(data));
  }
}

// Usage:
const mediator = new Mediator();
mediator.subscribe("formSubmitted", data => console.log("Form data:", data));
mediator.publish("formSubmitted", { name: "Alice" });
Enter fullscreen mode Exit fullscreen mode

Modern use: micro-frontends, plugin architectures, component islands in partial hydration setups.


🪠 Flyweight

Frontend visuals can get expensive. Especially in apps that render thousands of similar objects. Creating a fresh component for every node on a map or every marker on a chart? That’s waste. Flyweight helps by reusing shared objects instead.

class IconFactory {
  cache = {};
  getIcon(type) {
    return this.cache[type] ||= createIcon(type);
  }
}

const factory = new IconFactory();
const iconA = factory.getIcon("warning");
const iconB = factory.getIcon("warning"); // same reference
Enter fullscreen mode Exit fullscreen mode

Flyweight keeps your memory footprint lean and your UI responsive. Great for dashboards, grids, and canvas heavy UIs.


🛎 Command

I once watched someone record a macro in Excel and thought, "Why don’t we do that more in apps?" The Command pattern lets you encapsulate operations and treat them like data.

class Command {
  constructor(execute, undo) {
    this.execute = execute;
    this.undo = undo;
  }
}

// Example: change a value with undo
let state = { count: 0 };

const incrementCommand = new Command(
  () => { state.count += 1; console.log("+1", state.count); },
  () => { state.count -= 1; console.log("Undo +1", state.count); }
);

incrementCommand.execute();
incrementCommand.undo();
Enter fullscreen mode Exit fullscreen mode

You now have actions as objects. Useful for macro recording, redo stacks, custom UIs, and low-code builders.


🕸 Chain of Responsibility

Sometimes a single validator isn't enough. You want to run a request through a series of checks auth, input format, business rules, and stop the process if any fail. That’s Chain of Responsibility in action.

class Handler {
  setNext(handler) {
    this.next = handler;
    return handler;
  }
  handle(request) {
    if (this.canHandle(request)) {
      return this.process(request);
    } else if (this.next) {
      return this.next.handle(request);
    }
    return null;
  }
}

class AuthHandler extends Handler {
  canHandle(req) { return req.user !== undefined; }
  process(req) { console.log("Auth passed"); return true; }
}

class ValidationHandler extends Handler {
  canHandle(req) { return req.body && req.body.email?.includes("@"); }
  process(req) { console.log("Validation passed"); return true; }
}

// Set up the chain
const auth = new AuthHandler();
const validate = new ValidationHandler();
auth.setNext(validate);

auth.handle({ user: "admin", body: { email: "test@example.com" } });
Enter fullscreen mode Exit fullscreen mode

Now imagine you add an EmailFormatHandler or a PasswordStrengthHandler. Each step gets its own responsibility, and if one fails, the rest don't need to run. This is what makes Chain of Responsibility ideal for scalable, readable validation flows.


🏗 Builder (Yes, for UI)

You’re already using it. If you’re writing fluent APIs, chaining configuration methods, or building JSX trees with dynamic inputs, you’ve met the Builder. It turns configuration into composition.

class CardBuilder {
  constructor() {
    this.card = {};
  }
  withTitle(title) {
    this.card.title = title;
    return this;
  }
  withButton(label) {
    this.card.button = label;
    return this;
  }
  render() {
    return `<div class="card">
      <h2>${this.card.title}</h2>
      <button>${this.card.button}</button>
    </div>`;
  }
}

const card = new CardBuilder()
  .withTitle("Welcome")
  .withButton("Start")
  .render();

console.log(card);
Enter fullscreen mode Exit fullscreen mode

Forget the Java style ceremony. In frontend land, Builder is DSL magic hiding in plain sight, especially when you're wiring up reusable components from raw data or user-generated configs.


🧩 Why This Still Matters

These patterns aren’t here for nostalgia. They’re the muscle memory your code uses when the shiny parts fall apart.

They’re the reason you don’t rewrite half your app when a new feature drops. The reason your undo stack works. The reason your components don’t scream across the DOM.

Frameworks come and go. These stay useful.


You don’t need to be retro to be resilient.

Just smart enough to reach for the tool that fits—even if it hasn’t trended in a while.


📌 TL;DR — Design Patterns at a Glance

Pattern Solves Real-World Use Case
🌀 Memento Undo/redo, state snapshots Form builders, collaborative editors
🎭 Mediator Component comms without chaos Micro-frontends, plugin systems
🪠 Flyweight Memory bloat from similar objects Canvas UIs, dashboards, virtualized lists
🛎 Command Treating actions as objects Macros, redo stacks, automation workflows
🕸 Chain of Responsibility Modular request handling Validation chains, API filters, event pipes
🏗 Builder (UI) Structured creation of complex UIs DSLs, JSX factories, CMS page generators

🔗 Further Reading


Trends shift. Tools evolve. But complexity sticks around.

These patterns aren’t flashy, but they hold the line when your architecture starts creaking and the deadline's already passed. They’re not a flex. They’re a fallback. And sometimes, that fallback is what keeps the sprint from burning down.

So keep them close. Not for prestige. For peace of mind.

What’s your favorite design pattern? Is there one I left out you think everyone should know about? If so, drop a comment below.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.