After 4+ years of building web applications, these are the code snippets that have saved me countless hours of debugging and repetitive coding
Building web applications means solving the same problems over and over again. Date formatting, array manipulation, API calls, form validation — these tasks appear in every project, yet we often find ourselves rewriting the same logic repeatedly.
Over the years, I've collected a set of utility functions that I now copy into every new project. They're simple, reliable, and handle the edge cases that often cause bugs in production.
Here are the 10 JavaScript utilities that have become essential to my development workflow:
1. Sleep Function (Better Than setTimeout)
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Usage
async function example() {
console.log('Starting...');
await sleep(2000);
console.log('2 seconds later');
}
Why I use this: Perfect for adding delays in async functions, testing loading states, or rate limiting API calls. Much cleaner than nested setTimeout callbacks.
2. Deep Clone Object (Handles Nested Objects)
const deepClone = (obj) => {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => deepClone(item));
if (typeof obj === 'object') {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
};
// Usage
const original = { user: { name: 'John', age: 30 }, items: [1, 2, 3] };
const copied = deepClone(original);
Why I use this: JavaScript's spread operator only does shallow copying. This handles nested objects and arrays properly, preventing those tricky reference bugs.
3. Debounce Function (Performance Saver)
const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
};
// Usage
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((query) => {
// API call here
console.log('Searching for:', query);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Why I use this: Essential for search inputs, API calls, and any event that fires frequently. Prevents performance issues and reduces server load.
4. Format Currency (Internationalization Ready)
const formatCurrency = (amount, currency = 'USD', locale = 'en-US') => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: 2
}).format(amount);
};
// Usage
console.log(formatCurrency(1234.56)); // $1,234.56
console.log(formatCurrency(1234.56, 'EUR', 'de-DE')); // 1.234,56 €
Why I use this: Handles currency formatting for different locales automatically. No more manual string manipulation for prices.
5. Generate Random ID (UUID Alternative)
const generateId = (length = 8) => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
// Usage
const userId = generateId(); // "Kx8vN2mP"
const sessionId = generateId(16); // "Kx8vN2mPq7wZ3rY9"
Why I use this: Perfect for generating temporary IDs, keys for React components, or session identifiers. Lighter than UUID libraries.
6. Local Storage with Expiration
const storage = {
set: (key, value, expireInMinutes = 60) => {
const expireTime = new Date().getTime() + (expireInMinutes * 60 * 1000);
const item = { value, expireTime };
localStorage.setItem(key, JSON.stringify(item));
},
get: (key) => {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
const now = new Date().getTime();
if (now > item.expireTime) {
localStorage.removeItem(key);
return null;
}
return item.value;
},
remove: (key) => localStorage.removeItem(key)
};
// Usage
storage.set('user', { name: 'John' }, 30); // Expires in 30 minutes
const user = storage.get('user');
Why I use this: Prevents stale data issues with localStorage. Perfect for caching API responses or user preferences.
7. Retry Function (API Call Resilience)
const retry = async (fn, retries = 3, delay = 1000) => {
try {
return await fn();
} catch (error) {
if (retries > 0) {
await sleep(delay);
return retry(fn, retries - 1, delay * 2); // Exponential backoff
}
throw error;
}
};
// Usage
const fetchData = async () => {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('API call failed');
return response.json();
};
const data = await retry(fetchData, 3, 1000);
Why I use this: Makes API calls more robust. Handles temporary network issues and server hiccups automatically.
8. Object Validation (Type Safety)
const validate = (obj, schema) => {
const errors = [];
for (const [key, rules] of Object.entries(schema)) {
const value = obj[key];
if (rules.required && (value === undefined || value === null || value === '')) {
errors.push(`${key} is required`);
continue;
}
if (value !== undefined && rules.type && typeof value !== rules.type) {
errors.push(`${key} must be of type ${rules.type}`);
}
if (rules.minLength && value.length < rules.minLength) {
errors.push(`${key} must be at least ${rules.minLength} characters`);
}
if (rules.pattern && !rules.pattern.test(value)) {
errors.push(`${key} format is invalid`);
}
}
return { isValid: errors.length === 0, errors };
};
// Usage
const userSchema = {
email: { required: true, type: 'string', pattern: /^\S+@\S+\.\S+$/ },
age: { required: true, type: 'number' },
name: { required: true, type: 'string', minLength: 2 }
};
const result = validate({ email: '[email protected]', age: 25, name: 'John' }, userSchema);
Why I use this: Simple validation without heavy libraries. Perfect for form validation and API input checking.
9. Array Chunk (Pagination Helper)
const chunk = (array, size) => {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
};
// Usage
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const pages = chunk(items, 3); // [[1,2,3], [4,5,6], [7,8,9], [10]]
Why I use this: Essential for pagination, batch processing, or displaying items in rows. Simple but incredibly useful.
10. Format Date (Human Readable)
const formatDate = (date, options = {}) => {
const defaultOptions = {
year: 'numeric',
month: 'long',
day: 'numeric',
...options
};
return new Date(date).toLocaleDateString('en-US', defaultOptions);
};
const timeAgo = (date) => {
const now = new Date();
const diffInSeconds = Math.floor((now - new Date(date)) / 1000);
const intervals = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 }
];
for (const interval of intervals) {
const count = Math.floor(diffInSeconds / interval.seconds);
if (count >= 1) {
return `${count} ${interval.label}${count > 1 ? 's' : ''} ago`;
}
}
return 'just now';
};
// Usage
console.log(formatDate('2025-06-20')); // "June 20, 2025"
console.log(timeAgo('2025-06-19T10:00:00')); // "1 day ago"
Why I use this: Date formatting is needed in almost every app. These functions handle the most common use cases cleanly.
How to Use These Snippets
I keep these utilities in a utils.js
file in every project. Here's how I organize them:
// utils.js
export const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
export const deepClone = (obj) => { /* implementation */ };
export const debounce = (func, delay) => { /* implementation */ };
// ... rest of the functions
// In your components
import { sleep, debounce, formatCurrency } from './utils.js';
The Time-Saving Impact
Since I started using these utilities consistently:
- Faster development: No more googling "how to deep clone object JavaScript"
- Fewer bugs: These functions handle edge cases I used to forget
- Better code quality: Consistent patterns across all my projects
- Easier maintenance: Well-tested utilities mean more reliable apps
Bonus: Testing These Utils
Here's a simple test structure I use:
// utils.test.js
describe('Utility Functions', () => {
test('formatCurrency should format numbers correctly', () => {
expect(formatCurrency(1234.56)).toBe('$1,234.56');
});
test('debounce should delay function execution', async () => {
let count = 0;
const increment = debounce(() => count++, 100);
increment();
increment();
increment();
expect(count).toBe(0);
await sleep(150);
expect(count).toBe(1);
});
});
Final Thoughts
These 10 utilities might seem simple, but they solve real problems that every developer faces. The key is consistency — once you start using these patterns, you'll find yourself writing more reliable code faster.
Copy these into your next project and customize them to fit your needs. Your future self will thank you when you're not debugging the same issues over and over again.
What utilities do you find yourself rewriting in every project? Share your must-have code snippets in the comments below.
If you found this helpful, follow me for more practical web development tips and tricks.
Top comments (0)