Youssef Ameachaq's Blog

Youssef Ameachaq

How Engineers Keep Fighting Duplication


In my more than a decade in software engineering, I’ve seen endless new patterns and frameworks show up with one shared goal; to kill duplication. Every developer I’ve worked with hated it. We’ve all copied and pasted something “just this once,” and then paid the price later when a change broke one copy but not the other.

Over time, we’ve invented clever ways to avoid repeating ourselves: DRY, higher-order components, composition, closures, mixins, and many others. Each one is a tool in our ongoing battle against duplication.

DRY – Don’t Repeat Yourself

This is the first lesson almost every developer learns. If you write the same logic in multiple places, you’ll regret it when requirements change.

For example:

function formatDate(date) {
  return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate();
}

If you find this same snippet sprinkled across files, better to extract it once and reuse it. DRY is about keeping one source of truth so that when something changes, you only fix it in one place.

But it’s also about understanding what you’re repeating. Not every line that looks similar is true duplication. Sometimes two things just happen to look alike but represent different ideas; abstracting those too early can hurt more than help.

Higher-Order Components

In React, higher-order components (HOCs) let you share common logic between components without copy-pasting code.

For example, if several components need data fetching and loading states, you can wrap them with a HOC:

function withFetching(WrappedComponent) {
  return class extends React.Component {
    // handles fetching, loading, and errors
    render() {
      return <WrappedComponent {...this.props} data={this.state.data} />;
    }
  }
}

Now, every component that needs fetching logic can use withFetching(); no repeated boilerplate.

Composition

Composition means building functionality by combining small, focused pieces rather than inheriting large, complex ones.

React hooks are a great example:

function useToggle(initial) {
  const [on, setOn] = useState(initial);
  const toggle = () => setOn(o => !o);
  return { on, toggle };
}

Any component can now “compose” this toggle behaviour instead of writing it over and over again.

Composition keeps logic modular and reusable without forcing rigid hierarchies.

Closures

Closures let you bundle behaviour and state together neatly. They’re great for removing duplication where you need similar but independent instances of logic.

function makeCounter(start = 0) {
  let count = start;
  return {
    increment() { count++; return count; },
    get() { return count; }
  };
}

You can create as many counters as you want without rewriting the same increment logic.

Mixins

Mixins were an early pattern for sharing behaviour between classes or objects. They let you add reusable functionality without inheritance or copy-pasting.

In JavaScript, this often looks like:

const canLog = {
  log(message) {
    console.log(`[${this.name}] ${message}`);
  }
};

const user = { name: "Alice", ...canLog };
user.log("Signed in");

Mixins paved the way for modern composition and functional reuse patterns.

Locality of Behaviour and Why Duplication Isn’t Always Evil

In recent years, especially with tools like htmx, there’s been a bit of a shift in how we think about duplication.

Htmx encourages locality of behaviour; keeping logic close to where it’s used. Instead of spreading your logic across layers of abstractions or shared modules, sometimes it’s clearer and faster to duplicate a small piece of behaviour right where it belongs.

That might sound like heresy to “DRY” purists, but it’s practical. Local duplication can actually improve clarity and reduce mental overhead. When you open an HTML file and see all the behaviour right there, you don’t have to jump around the project trying to find where something is defined.

So, a small amount of duplication; especially in the name of keeping behaviour local and readable; can sometimes make code easier to work with.

Why We Still Fight Duplication

Duplication increases maintenance cost. Every repeated piece of logic is another thing you’ll have to fix or update later. It spreads knowledge around the codebase and makes change risky.

But there’s a balance. Abstracting too early or too aggressively creates another problem: unnecessary coupling. Suddenly, a change in one module affects others that shouldn’t care.

Good engineers learn to recognise this trade-off. They remove duplication when it represents shared behaviour; but they don’t chase “DRY” for its own sake.

When Duplication Is Okay

There are times when duplication is a good trade-off:

The Real Goal

Avoiding duplication isn’t about perfection; it’s about flexibility. The less you repeat logic, the easier it is to evolve your system when things change. But clarity and simplicity matter just as much.

In the end, good engineering is finding that sweet spot: reuse when it helps, duplicate when it keeps things clear.