I once worked on a codebase with a utils/ folder containing 47 files. Most of them had one function. Many of those functions were used exactly once. The team had taken "Don't Repeat Yourself" so literally that they created abstractions for everything.

The irony? Finding and using these utils was harder than just writing the code inline. DRY had become a burden.

Let me share what I've learned about when to create utilities—and when not to.

The Rule of Three

This is my main guideline: don't abstract until you've written similar code three times.

The first time you write something, just write it. The second time, you might notice similarities, but resist the urge. By the third time, you actually understand the pattern and can create a good abstraction.

// First time: just write it
const userName = user.firstName + ' ' + user.lastName;

// Second time: hmm, similar... still just write it
const authorName = author.firstName + ' ' + author.lastName;

// Third time: okay, there's a pattern
const reviewerName = reviewer.firstName + ' ' + reviewer.lastName;

// NOW create the utility
function getFullName(person) {
    return `${person.firstName} ${person.lastName}`;
}

Why wait? Because requirements change. Maybe the second use case actually needs middle names. Maybe the third needs "Last, First" format. If you abstract too early, you end up with:

// Over-engineered from premature abstraction
function getFullName(person, options = {}) {
    const { includeMiddle, lastFirst, separator = ' ' } = options;
    if (lastFirst) {
        return includeMiddle 
            ? `${person.lastName}${separator}${person.firstName}${separator}${person.middleName}`
            : `${person.lastName}${separator}${person.firstName}`;
    }
    return includeMiddle
        ? `${person.firstName}${separator}${person.middleName}${separator}${person.lastName}`
        : `${person.firstName}${separator}${person.lastName}`;
}

Now nobody knows how to use it without reading the source code. Three simple inline expressions would have been clearer.

Good Utilities Are Pure

The best utility functions:

  1. Take input, return output — no side effects
  2. Don't depend on external state — same input = same output
  3. Don't know about your business domain — work with primitives
// ✅ Good utility: pure, generic, predictable
function formatCurrency(amount, currency = 'USD') {
    return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency
    }).format(amount);
}

function debounce(fn, delay) {
    let timeoutId;
    return (...args) => {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn(...args), delay);
    };
}

function groupBy(array, key) {
    return array.reduce((groups, item) => {
        const group = item[key];
        groups[group] = groups[group] || [];
        groups[group].push(item);
        return groups;
    }, {});
}

// ❌ Bad utility: knows too much about your app
function formatUserPrice(user, product) {
    const discount = user.isPremium ? 0.1 : 0;
    const price = product.price * (1 - discount);
    return '$' + price.toFixed(2);
}
// This isn't a utility, it's business logic wearing a utility costume

When Copy-Paste Is Fine

Sometimes duplication is the right call:

// This doesn't need to be a utility
const isAdult = user.age >= 18;

// Neither does this
const initials = name.split(' ').map(n => n[0]).join('');

// Two lines of code used in one place? Just inline it.
const sortedUsers = [...users].sort((a, b) => a.name.localeCompare(b.name));

"But what if I need it again?" Then extract it when you actually need it. The code isn't going anywhere.

Lodash vs Rolling Your Own

Lodash exists. It's battle-tested. If you need debounce, throttle, groupBy, chunk, etc., just use Lodash.

import { debounce, groupBy, chunk } from 'lodash-es';
// Don't reinvent these

When to write your own:

When I join a project, a bloated utils/ folder is a red flag. It usually means the team abstracts things too eagerly. A small set of well-used utilities is a sign of maturity.

Organizing Utilities

If you do create utilities, organize them by what they operate on:

utils/
  date.js      // formatDate, parseDate, daysUntil
  string.js    // capitalize, truncate, slugify
  array.js     // groupBy, chunk, unique
  currency.js  // formatCurrency, parseCurrency
  validation.js // isEmail, isPhone, isURL

Not by feature. utils/userUtils.js usually contains business logic that belongs in a service, not a utility.

My Actual Utils File

After years of coding, here's what I actually reuse across projects:

// The classics
export const debounce = (fn, ms) => { /* ... */ };
export const throttle = (fn, ms) => { /* ... */ };

// Formatting
export const formatDate = (date, locale = 'en-US') => 
    new Date(date).toLocaleDateString(locale);

export const formatCurrency = (amount, currency = 'USD') =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);

// Validation
export const isEmail = (str) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);

// Helpers
export const sleep = (ms) => new Promise(r => setTimeout(r, ms));
export const randomId = () => Math.random().toString(36).slice(2);

// That's basically it. Maybe 10-15 functions total.

Everything else either comes from a library or gets written inline because it's specific to the project.

The Bottom Line

Write code inline until you can't stand the duplication anymore. Then, and only then, extract a utility. Keep it pure, keep it simple, and resist the urge to make it "flexible" with options and flags.

A good utility folder is small. If yours is growing out of control, you're probably abstracting too much.

← Back to JavaScript Articles

Back to Home