JavaScript Utility Functions Every Web Project Should Have on Hand

Every JavaScript project ends up needing the same set of helper functions. String truncation. Date formatting. Array deduplication. Debouncing. Deep object cloning. These are not exciting to write, and they are not complicated - but developers write them from scratch in project after project instead of maintaining a shared utility library.

This is a reference of the functions that appear in most web projects. They are vanilla JavaScript - no dependencies - and can be dropped into any project or adapted for your specific needs.

String Utilities

String manipulation is among the most frequent sources of ad-hoc utility functions. These cover the most common cases.

Truncate to length with ellipsis:

function truncate(str, maxLength, suffix = '...') {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength - suffix.length) + suffix;
}
// truncate('Hello, world!', 8) => 'Hello...'

Convert to URL-friendly slug:

function slugify(str) {
  return str
    .toLowerCase()
    .trim()
    .replace(/[^\w\s-]/g, '')
    .replace(/[\s_-]+/g, '-')
    .replace(/^-+|-+$/g, '');
}
// slugify('Hello World! How Are You?') => 'hello-world-how-are-you'

Capitalize first letter of each word:

function titleCase(str) {
  return str.toLowerCase().replace(/\b\w/g, char => char.toUpperCase());
}
// titleCase('hello world') => 'Hello World'

Strip HTML tags (for display, not security - sanitize separately):

function stripHtml(str) {
  return str.replace(/<[^>]*>/g, '');
}

Count words in a string:

function wordCount(str) {
  return str.trim().split(/\s+/).filter(Boolean).length;
}

Developer writing JavaScript utility functions on a laptop
Photo by Lukas Blazek on Pexels

Date and Time Utilities

Date handling in JavaScript is notoriously awkward. These functions handle the most common formatting and calculation tasks without requiring a library.

Format a date as YYYY-MM-DD:

function formatDate(date = new Date()) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}
// formatDate(new Date('2026-04-01')) => '2026-04-01'

Relative time (e.g., "3 days ago"):

function relativeTime(date) {
  const seconds = Math.floor((new Date() - new Date(date)) / 1000);
  const intervals = [
    [31536000, 'year'],
    [2592000, 'month'],
    [86400, 'day'],
    [3600, 'hour'],
    [60, 'minute'],
  ];
  for (const [interval, label] of intervals) {
    const count = Math.floor(seconds / interval);
    if (count >= 1) {
      return `${count} ${label}${count > 1 ? 's' : ''} ago`;
    }
  }
  return 'just now';
}

Add days to a date:

function addDays(date, days) {
  const result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}

Check if a date is today:

function isToday(date) {
  const d = new Date(date);
  const today = new Date();
  return d.getDate() === today.getDate()
    && d.getMonth() === today.getMonth()
    && d.getFullYear() === today.getFullYear();
}

Array Utilities

JavaScript's built-in array methods are good but frequently need composition for common operations.

Remove duplicates from a flat array:

function unique(arr) {
  return [...new Set(arr)];
}
// unique([1, 2, 2, 3, 3, 3]) => [1, 2, 3]

Group array of objects by a key:

function groupBy(arr, key) {
  return arr.reduce((groups, item) => {
    const group = item[key];
    groups[group] = groups[group] || [];
    groups[group].push(item);
    return groups;
  }, {});
}
// groupBy([{type:'a', val:1},{type:'b', val:2},{type:'a', val:3}], 'type')
// => { a: [{type:'a', val:1},{type:'a', val:3}], b: [{type:'b', val:2}] }

Flatten a nested array to any depth:

function flatDeep(arr, depth = Infinity) {
  return arr.flat(depth);
}
// flatDeep([[1, [2, [3]]]]) => [1, 2, 3]

Chunk array into groups of N:

function chunk(arr, size) {
  return Array.from({ length: Math.ceil(arr.length / size) },
    (_, i) => arr.slice(i * size, i * size + size));
}
// chunk([1,2,3,4,5], 2) => [[1,2],[3,4],[5]]

Sort array of objects by a property:

function sortBy(arr, key, direction = 'asc') {
  return [...arr].sort((a, b) => {
    if (a[key] < b[key]) return direction === 'asc' ? -1 : 1;
    if (a[key] > b[key]) return direction === 'asc' ? 1 : -1;
    return 0;
  });
}

Code editor showing JavaScript array utility functions
Photo by Rashed Paykary on Pexels

DOM Utilities

DOM manipulation utilities reduce the verbosity of common browser operations.

Query selector with default:

function $(selector, parent = document) {
  return parent.querySelector(selector);
}

function $$(selector, parent = document) {
  return [...parent.querySelectorAll(selector)];
}

Add/remove/toggle classes with multiple classes:

function addClass(el, ...classes) {
  el.classList.add(...classes);
}

function removeClass(el, ...classes) {
  el.classList.remove(...classes);
}

function toggleClass(el, className, force) {
  el.classList.toggle(className, force);
}

Check if an element is visible in the viewport:

function isInViewport(el) {
  const rect = el.getBoundingClientRect();
  return rect.top >= 0
    && rect.left >= 0
    && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
    && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
}

Get offset position relative to document:

function getOffset(el) {
  const rect = el.getBoundingClientRect();
  return {
    top: rect.top + window.scrollY,
    left: rect.left + window.scrollX,
  };
}

Async and Function Utilities

These utilities control when and how functions execute, which is essential for performance and UX.

Debounce (delay execution until N ms after last call):

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
// const debouncedSearch = debounce(search, 300);
// input.addEventListener('input', debouncedSearch);

Throttle (execute at most once per N ms):

function throttle(fn, limit) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= limit) {
      lastCall = now;
      return fn.apply(this, args);
    }
  };
}

Sleep (async pause):

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
// await sleep(1000); // wait 1 second

Retry an async function (with backoff):

async function retry(fn, attempts = 3, delay = 1000) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === attempts - 1) throw err;
      await sleep(delay * Math.pow(2, i));
    }
  }
}

Memoize (cache function results by argument):

function memoize(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

Object Utilities

Deep clone (without circular reference support - use structuredClone for that in modern environments):

function deepClone(obj) {
  if (typeof structuredClone === 'function') return structuredClone(obj);
  return JSON.parse(JSON.stringify(obj));
}

Safely get a nested value (without optional chaining, for compatibility):

function get(obj, path, defaultValue = undefined) {
  const keys = path.split('.');
  let result = obj;
  for (const key of keys) {
    if (result == null) return defaultValue;
    result = result[key];
  }
  return result !== undefined ? result : defaultValue;
}
// get(user, 'address.city', 'Unknown') => 'San Francisco' or 'Unknown'

Pick specific keys from an object:

function pick(obj, keys) {
  return keys.reduce((acc, key) => {
    if (key in obj) acc[key] = obj[key];
    return acc;
  }, {});
}
// pick({a:1, b:2, c:3}, ['a', 'c']) => {a:1, c:3}

Omit specific keys from an object:

function omit(obj, keys) {
  return Object.fromEntries(
    Object.entries(obj).filter(([k]) => !keys.includes(k))
  );
}
// omit({a:1, b:2, c:3}, ['b']) => {a:1, c:3}

Validation Utilities

Input validation belongs at system boundaries - wherever your code receives data from the outside world. These lightweight validators handle the most common cases without requiring a full schema validation library.

Check if a string is a valid email format:

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

Check if a string is a valid URL:

function isValidUrl(str) {
  try {
    new URL(str);
    return true;
  } catch {
    return false;
  }
}

Check if a value is empty (null, undefined, empty string, empty array, empty object):

function isEmpty(value) {
  if (value == null) return true;
  if (typeof value === 'string') return value.trim().length === 0;
  if (Array.isArray(value)) return value.length === 0;
  if (typeof value === 'object') return Object.keys(value).length === 0;
  return false;
}

Check if a number is within a range:

function inRange(num, min, max) {
  return num >= min && num <= max;
}

These validators are intentionally simple - they check format and type, not business rules. Business rule validation (is this email registered? is this quantity available in inventory?) belongs in your application layer, not in utility functions. Keeping format validation separate from business rule validation makes both easier to test and reason about independently.

A common pattern is to compose these validators into a simple form validation helper that returns an object of field errors - each key is a field name, each value is an error message or null. This keeps validation logic centralized and makes it easy to display field-level errors in a UI without scattering validation checks throughout your component code.

Developer testing JavaScript code in a browser developer console
Photo by Markus Spiske on Pexels

Using This as a Starting Point

These functions are intentionally minimal and dependency-free. For projects where you need more comprehensive utilities, Lodash at lodash.com is the most mature option and supports tree-shaking so you only bundle what you use. The Remeda library at remedajs.com is a modern TypeScript-first alternative. For date handling specifically, date-fns at date-fns.org is widely used and tree-shakeable.

For reference on modern JavaScript built-in methods that have replaced some historical utility needs, MDN Web Docs at developer.mozilla.org/en-US/docs/Web/JavaScript is the authoritative source. Many operations that previously required utility libraries - flat(), findIndex(), structuredClone(), Object.entries() - are now standard and well-supported.

The team at 137Foundry builds web applications where these patterns are applied consistently across projects. The frontend development page covers our approach to JavaScript development and the standards we apply to production code. If you are building a web application and want help with architecture or implementation, the contact page is a good starting point.

For TypeScript-typed versions of these utilities, the utility-types library at github.com/piotrwitek/utility-types provides TypeScript type helpers that pair well with implementations like these.

Need help with Code Snippets?

137Foundry builds custom software, AI integrations, and automation systems for businesses that need real solutions.

Book a Free Consultation View Services