A practical refactoring duplicated code example for front-end developers, with JavaScript patterns, trade-offs, and tips for cleaner DRY code.
You spot it when the third copy lands. Two similar functions become three, then five, and suddenly your JavaScript file looks like it has been copy-pasted by a very determined raccoon. A good refactoring duplicated code example is less about tidying for the sake of it and more about making future changes safer, faster, and far less annoying.
For front-end developers, duplicated code often sneaks in through event handlers, UI rendering, fetch logic, and form validation. The awkward bit is that duplication does not always look bad at first. Sometimes repeating yourself is fine. Sometimes it is a warning light. The trick is knowing the difference.
Let’s start with a very normal piece of front-end code. Imagine a small shop interface with buttons that update item quantities in the basket.
“`js function increaseProductA() { const input = document.querySelector(‘#product-a-qty’); const price = document.querySelector(‘#product-a-price’); let quantity = Number(input.value); quantity += 1; input.value = quantity; price.textContent = `£${(quantity * 12).toFixed(2)}`; }
function increaseProductB() { const input = document.querySelector(‘#product-b-qty’); const price = document.querySelector(‘#product-b-price’); let quantity = Number(input.value); quantity += 1; input.value = quantity; price.textContent = `£${(quantity * 8).toFixed(2)}`; } “`
This works. That matters. Refactoring is not a prize for making code look cleverer than it needs to be.
But both functions share the same shape. They query the DOM, read a value, update it, and recalculate a price. The only real differences are the selectors and unit price. If the pricing format changes later, you now have to update every copy. That is how tiny changes turn into bug hunts.
Here is the refactored version.
“`js function increaseProduct(quantitySelector, priceSelector, unitPrice) { const input = document.querySelector(quantitySelector); const price = document.querySelector(priceSelector); let quantity = Number(input.value); quantity += 1; input.value = quantity; price.textContent = `£${(quantity * unitPrice).toFixed(2)}`; }
function increaseProductA() { increaseProduct(‘#product-a-qty’, ‘#product-a-price’, 12); }
function increaseProductB() { increaseProduct(‘#product-b-qty’, ‘#product-b-price’, 8); } “`
Now the shared behaviour lives in one place. Product-specific details are passed in as arguments. Same result, less repetition, fewer places to break.
The obvious win is maintenance. If you later want to clamp the minimum quantity, add analytics tracking, or change currency formatting, you make that change once.
There is also a readability benefit, which sounds boring until you are staring at a file at 4:57 pm on a Friday. Duplicated logic makes codebases feel bigger than they are. A well-named function gives that repeated behaviour a proper home.
That said, DRY can be overdone. If two code blocks look similar but are likely to evolve differently, forcing them into one abstraction can create a messier result. Shared code is helpful when the behaviour is genuinely shared, not merely wearing a similar outfit.
Not all repetition deserves a refactor. Sometimes duplication is cheaper than abstraction.
If the repeated code is tiny, appears only twice, and has no history of changing, leave it alone for now. If the same update would need to be made in multiple places, or if you are repeating an entire pattern rather than a couple of lines, that is usually a better candidate.
A useful rule is this: refactor when duplication increases the chance of inconsistent behaviour. Front-end code is especially prone to that with UI states. One button disables correctly, another almost does, and a third was clearly written after coffee wore off.
The previous version is better, but we can go further if the UI keeps growing. Suppose there are several products. Creating one wrapper function per item still adds boilerplate.
“`js function updateProductQuantity({ quantitySelector, priceSelector, unitPrice, change }) { const input = document.querySelector(quantitySelector); const price = document.querySelector(priceSelector);
let quantity = Number(input.value); quantity = Math.max(0, quantity + change);
input.value = quantity; price.textContent = `£${(quantity * unitPrice).toFixed(2)}`; }
document.querySelector(‘#product-a-increase’).addEventListener(‘click’, () => { updateProductQuantity({ quantitySelector: ‘#product-a-qty’, priceSelector: ‘#product-a-price’, unitPrice: 12, change: 1 }); });
document.querySelector(‘#product-b-increase’).addEventListener(‘click’, () => { updateProductQuantity({ quantitySelector: ‘#product-b-qty’, priceSelector: ‘#product-b-price’, unitPrice: 8, change: 1 }); }); “`
This version handles more variation without creating a new function for every product action. It is more flexible, though slightly more verbose at the call site. That is the trade-off. You remove duplication, but you also introduce a more generic API. Worth it if the pattern repeats a lot. Probably overkill if you have exactly two buttons and they are never changing.
Duplication often shows up in rendering logic too. Here is a common case.
“`js function renderSuccess(message) { const box = document.querySelector(‘.message’); box.textContent = message; box.classList.remove(‘error’); box.classList.add(‘success’); box.hidden = false; }
function renderError(message) { const box = document.querySelector(‘.message’); box.textContent = message; box.classList.remove(‘success’); box.classList.add(‘error’); box.hidden = false; } “`
Again, both functions are doing almost the same thing.
“`js function renderMessage(message, type) { const box = document.querySelector(‘.message’); box.textContent = message; box.classList.remove(‘success’, ‘error’); box.classList.add(type); box.hidden = false; }
renderMessage(‘Saved successfully’, ‘success’); renderMessage(‘Something went wrong’, ‘error’); “`
This is a tidy refactor because the abstraction is obvious. The function name tells you what it does, and the varying part is just the message type.
That is usually the sweet spot. Good refactoring tends to make code more boring, not more magical.
If your extracted function needs six booleans, three optional parameters, and a small prayer, you may have combined things that should stay separate. A decent abstraction reduces mental load. A bad one hides behaviour behind vague names like `handleData` or `processUIStuff`, which is the coding equivalent of shoving cables into a drawer.
When you refactor duplicated code, name the shared behaviour after what it actually does. `updateProductQuantity` is better than `manageProduct`. `renderMessage` is better than `displayItem`. Specific names make reused code feel safer because the intent is clear.
Start by identifying what is truly repeated and what changes between instances. The repeated part becomes the body of your shared function. The changing part becomes parameters, configuration, or data.
Then test the smallest possible refactor. Do not rewrite half the file because you found two similar lines. Replace one repeated block, run the code, and check that behaviour still matches. If the abstraction feels forced, back out and keep it simpler.
This is especially useful in front-end work because DOM code can be fragile. A refactor that looks neat but obscures selectors, timing, or event flow can make debugging worse. Cleaner code should still be easy to trace.
Sometimes the right move is to wait.
If two components happen to share logic today but are part of different features, refactoring too early can couple them unnecessarily. The same goes for code that is still being explored. During prototyping, duplication can be a perfectly reasonable way to learn what shape the final abstraction should take.
There is also the beginner trap of thinking all repetition is bad. It is not. Repeating a clear line or two can be easier to understand than introducing a helper function that saves three lines but adds indirection. The goal is not fewer lines at all costs. The goal is code that is easier to change without surprises.
A solid refactoring duplicated code example shows more than a before-and-after snippet. It shows a habit of thinking. You are asking: what behaviour repeats, what varies, and what version of this code will be easier to live with next month?
That mindset pays off quickly in front-end projects, where small UI changes can touch event handlers, state updates, and rendering all at once. Remove the right duplication and your code gets lighter, calmer, and less likely to bite back.
If you are unsure whether to refactor, do the boring test: imagine changing the same behaviour in three places next week. If that sounds tedious, the code is probably asking for help.