Back to KB
Difficulty
Intermediate
Read Time
8 min
JavaScript Performance: 8 Fixes That Actually Matter (2026)
By Codcompass TeamΒ·Β·8 min read
Not all performance optimizations are worth your time. These 8 are.
How to Measure (Before You Optimize!)
// Rule #1: Never optimize without measuring first
// Browser DevTools:
// β Performance tab: Record, analyze flame chart
// β Network tab: Waterfall view of requests
// β Memory tab: Heap snapshots, allocation timeline
// β Coverage tab: See unused JS/CSS (huge quick wins!)
// Node.js:
console.time('operation');
doSomething();
console.timeEnd('operation'); // operation: 123.456ms
// More precise:
const start = process.hrtime.bigint();
doSomething();
const end = process.hrtime.bigint();
console.log(`Duration: ${Number(end - start) / 1e6}ms`);
// Professional benchmarking
const { performance, PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('startQuery');
runDatabaseQuery();
performance.mark('endQuery');
performance.measure('query', 'startQuery', 'endQuery');
Enter fullscreen mode Exit fullscreen mode
Fix #1: Debounce & Throttle Expensive Operations
// Problem: Search input fires on every keystroke
input.addEventListener('input', (e) => {
search(e.target.value); // Fires 50+ times per second!
});
// Solution A: Debounce β wait until user stops typing
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const smartSearch = debounce(search, 300); // Wait 300ms after last keystroke
input.addEventListener('input', (e) => smartSearch(e.target.value));
// Solution B: Throttle β run at most once per interval
function throttle(fn, interval) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
fn.apply(this, args);
}
};
}
const throttledScroll = throttle(logScrollPosition, 100); // Max once per 100ms
window.addEventListener('scroll', throttledScroll);
// When to use which?
// Debounce: search, resize calculations, form validation
// Throttle: scroll handlers, button clicks, mousemove tracking
// Modern: requestAnimationFrame for visual updates
function rafThrottle(fn) {
let rafId = null;
return function (...args) {
if (rafId === null) {
rafId = requestAnimationFrame(() => {
fn.apply(this, args);
rafId = null;
});
}
};
}
Enter fullscreen mode Exit fullscreen mode
Fix #2: Lazy Load Everything Possible
<!-- Images: native lazy loading -->
<img src="photo.jpg" loading="lazy" alt="..." width="800" height="600" />
<!-- Always include width/height to prevent layout shift! -->
<!-- Dynamic imports (code splitting) -->
<script type="module">
// Don't load heavy libraries until needed
document.getElementById('chart-btn').addEventListener('click', async () => {
const { Chart } = await import('./chart-lib.js');
new Chart(ctx, config);
});
// Route-based code splitting (React/Vue do this automatically)
const Dashboard = () => import('./Dashboard.vue');
</script>
<!-- Intersection Observer for "load when visible" -->
<div class="lazy-section" data-src="/api/section-data"></div>
<script>
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadSection(entry.target);
observer.unobserve(entry.target); // Only load once!
}
});
},
{ rootMargin: '200px' } // Start loading 200px before visible
);
document.quer
π Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all 635+ tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
