JavaScript Performance: 8 Fixes That Actually Matter (2026)
Not all optimizations are worth your time. These 8 are.
The Hierarchy of Performance
Before optimizing, ask:
1. Is anyone actually complaining about speed?
2. Have you measured where the slowdown is?
3. Is this optimization worth the complexity?
Rule of thumb: Optimize the hot path first.
Fix #1: Debounce and Throttle User Events
The #1 performance mistake I see: running expensive operations on every keystroke/scroll.
// ❌ BAD — Runs on every keystroke
input.addEventListener('input', (e) => {
searchAPI(e.target.value); // API call on every key press!
});
// ✅ GOOD — Waits until user stops typing
function debounce(fn, delay = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
input.addEventListener('input', debounce((e) => {
searchAPI(e.target.value);
}, 300));
// ✅ GOOD — Runs at most once per interval (for scroll/resize)
function throttle(fn, interval = 100) {
let lastTime = 0;
return (...args) => {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn(...args);
}
};
}
window.addEventListener('scroll', throttle(checkScrollPosition, 100));
Impact: Can reduce function calls from thousands to dozens per second.
Fix #2: Lazy Load Images and Components
<!-- Just add loading="lazy" -->
<img src="photo.jpg" alt="..." loading="lazy" width="800" height="600">
<!-- For background images or dynamic content -->
<div data-src="heavy-image.jpg" class="lazy"></div>
// Intersection Observer (no library needed)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target;
if (el.dataset.src) el.src = el.dataset.src;
if (el.dataset.style) el.style.backgroundImage = `url(${el.dataset.style})`;
el.classList.add('loaded');
observer.unobserve(el);
}
});
}, { rootMargin: '100px' }); // Start loading 100px before visible
document.querySelectorAll('.lazy').forEach(el => observer.observe(el));
Impact: Reduces initial page load by 30-70% for image-heavy pages.
Fix #3: Use requestAnimationFrame for Visual Updates
// ❌ BAD — May cause layout thrashing
function updatePosition() {
element.style.left = calculateNewX();
element.style.top = calculateNewY();
requestAnimationFrame(updatePosition);
}
// ✅ GOOD — Syncs with browser's paint cycle
function updatePosition() {
const x = calculateNewX();
const y = calculateNewY();
requestAnimationFrame(() => {
element.style.transform = `translate(${x}px, ${y}px)`;
});
requestAnimationFrame(updatePosition);
}
Also use requestIdleCallback for non-urgent work:
// Do heavy work when browser is idle
requestIdleCallback(deadline => {
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
processTask(tasks.shift());
}
});
Fix #4: Avoid Layout Thrashing
Layout thrashing = forcing browser to recalculate layout repeatedly.
// ❌ BAD — Reads/writes in loop (forces reflow each iteration)
elements.forEach(el => {
console.log(el.offsetHeight); // READ → forces reflow
el.style.width = el.offsetWidth + 10 + 'px'; // WRITE → then read again
});
// ✅ GOOD — Batch reads, then batch writes
// First: all reads
const heights = elements.map(el => el.offsetHeight);
// Then: all writes
elements.forEach((el, i) => {
el.style.width = heights[i] + 10 + 'px';
});
Tools: Chrome DevTools → Rendering → "Paint flashing" to see repaints.
Fix #5: Efficient DOM Manipulation
// ❌ BAD — Multiple DOM insertions
const list = document.getElementById('list');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item.name;
list.appendChild(li); // Reflow each time
});
// ✅ GOOD — Single insertion with DocumentFragment
const fragment = new DocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item.name;
fragment.appendChild(li);
});
list.appendChild(fragment); // Single reflow
// ✅ BETTER — innerHTML (for simple cases)
list.innerHTML = items.map(item =>
`<li>${escapeHtml(item.name)}</li>`
).join('');
For frequent updates, consider Virtual DOM libraries or template literals with smart diffing.
Fix #6: Optimize Loops
const arr = [/* 10,000+ items */];
// ❌ SLOW — forEach with callback overhead
arr.forEach(item => { /* ... */ });
// ❌ SLOWER — for...of (iterator protocol overhead)
for (const item of arr) { /* ... */ };
// ✅ FASTEST — Traditional for loop (for hot paths)
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// ...
}
// ✅ ALSO FAST — Length cached (if array might change)
for (let i = 0, len = arr.length; i < len; i++) {
// ...
}
Reality check: For most apps, this difference is negligible. Only optimize loops that run 10,000+ times per second.
Fix #7: Memoize Expensive Computations
// Simple memoization
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Usage
const expensiveCalculation = memoize((n) => {
console.log('Computing...');
return n * n * n; // Imagine this is slow
});
expensiveCalculation(5); // "Computing..." → 125
expensiveCalculation(5); // → 125 (from cache!)
expensiveCalculation(5); // → 125 (from cache!)
// React equivalent: useMemo / React.memo
const ExpensiveComponent = React.memo(({ data }) => {
const result = useMemo(() => processData(data), [data]);
return <div>{result}</div>;
});
Fix #8: Web Workers for Heavy Computation
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ type: 'PROCESS', data: largeDataset });
worker.onmessage = (e) => {
if (e.data.type === 'RESULT') {
renderResults(e.data.result);
}
};
// worker.js
self.onmessage = (e) => {
if (e.data.type === 'PROCESS') {
const result = heavyComputation(e.data.data);
self.postMessage({ type: 'RESULT', result });
}
};
Use for: Image processing, crypto operations, large dataset analysis, real-time calculations.
What NOT To Optimize
❌ Don't optimize:
• Code readability for marginal gains
• One-time operations (startup, config loading)
• Code that runs < 10ms total
• Before measuring (you'll guess wrong 80% of the time)
✅ DO optimize:
• Render loops (animation, scroll handlers)
• Large list rendering (virtualize instead)
• API response processing (big JSON payloads)
• Event handlers that fire frequently
• Anything users complain about being "slow"
Measuring Performance
// Quick measurement
console.time('operation');
doSomething();
console.timeEnd('operation'); // operation: 123ms
// More precise
const start = performance.now();
doSomething();
const duration = performance.now() - start;
console.log(`Took ${duration.toFixed(2)}ms`);
// In production: Performance Observer
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 100) {
logSlowOperation(entry.name, entry.duration);
}
}
});
observer.observe({ entryTypes: ['measure'] });
What's the biggest performance win you've ever found?
Follow @armorbreak for more practical JS guides.
Top comments (0)