Performance Optimization
Make your JavaScript faster. Measure, identify bottlenecks, then optimize.
Measure First
Never optimize without measuring. Data over intuition.
// Rule #1: Measure, don't guess.
// Your intuition about what's slow is often wrong.
// Performance API — precise timing:
performance.mark("start");
expensiveOperation();
performance.mark("end");
performance.measure("operation", "start", "end");
const duration = performance.getEntriesByName("operation")[0].duration;
console.log(`Took ${duration.toFixed(2)}ms`);
// Quick benchmarking:
console.time("sort");
array.sort((a, b) => a - b);
console.timeEnd("sort"); // "sort: 12.5ms"
// Web Vitals (what Google measures):
// LCP (Largest Contentful Paint) — loading speed
// FID (First Input Delay) — interactivity
// CLS (Cumulative Layout Shift) — visual stability
// INP (Interaction to Next Paint) — responsiveness
// Chrome DevTools Performance panel:
// 1. Open DevTools → Performance
// 2. Click Record → interact with page → Stop
// 3. Analyze flame chart — tall bars = slow code
// 4. Look for "Long Tasks" (>50ms blocks main thread)Runtime Performance
Make your code execute faster.
// 1. Debounce & Throttle — limit expensive operations:
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// Only fires after user stops typing for 300ms:
input.addEventListener("input", debounce(search, 300));
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// At most once every 100ms during scroll:
window.addEventListener("scroll", throttle(onScroll, 100));// 2. Avoid blocking the main thread:
// BAD — processes everything synchronously:
function processAll(items) {
items.forEach(item => heavyCompute(item)); // blocks UI
}
// GOOD — chunk work across frames:
function processInChunks(items, chunkSize = 50) {
let i = 0;
function next() {
const end = Math.min(i + chunkSize, items.length);
while (i < end) heavyCompute(items[i++]);
if (i < items.length) requestAnimationFrame(next);
}
next();
}
// 3. Use Web Workers for heavy computation:
const worker = new Worker("heavy-task.js");
worker.postMessage(data);
worker.onmessage = (e) => updateUI(e.data);DOM Optimization
The DOM is slow — minimize interactions with it.
// BAD — DOM access in loop (100 reflows!):
for (let i = 0; i < 100; i++) {
const div = document.createElement("div");
div.textContent = items[i];
container.appendChild(div); // triggers reflow each time!
}
// GOOD — batch with DocumentFragment:
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement("div");
div.textContent = items[i];
fragment.appendChild(div); // no reflow
}
container.appendChild(fragment); // ONE reflow
// ALSO GOOD — innerHTML (for simple content):
container.innerHTML = items.map(i => `<div>${i}</div>`).join("");Each DOM modification can trigger expensive layout recalculations. Batch changes to minimize reflows.
1 / 2
Loading Performance
Get code to the browser faster.
// 1. Code splitting — load only what's needed:
const AdminPanel = lazy(() => import("./AdminPanel"));
// 2. Preload critical resources:
// <link rel="preload" href="/font.woff2" as="font">
// <link rel="preconnect" href="https://api.example.com">
// 3. Lazy load images and components:
// <img loading="lazy" src="photo.jpg" />
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) loadImage(e.target);
});
});
// 4. Efficient data fetching:
// Parallel requests (not sequential):
// BAD:
const users = await fetch("/users").then(r => r.json());
const posts = await fetch("/posts").then(r => r.json());
// GOOD — parallel:
const [users, posts] = await Promise.all([
fetch("/users").then(r => r.json()),
fetch("/posts").then(r => r.json()),
]);
// 5. Cache API responses:
const cache = new Map();
async function cachedFetch(url) {
if (cache.has(url)) return cache.get(url);
const data = await fetch(url).then(r => r.json());
cache.set(url, data);
return data;
}Memory
Prevent leaks, reduce allocations.
// 1. Clean up event listeners:
function setup() {
const handler = () => update();
window.addEventListener("resize", handler);
// Return cleanup function:
return () => window.removeEventListener("resize", handler);
}
const cleanup = setup();
// Later: cleanup();
// 2. Clear references when done:
let bigData = loadLargeDataset();
processData(bigData);
bigData = null; // allow GC to collect
// 3. Use WeakMap/WeakSet for metadata:
// Keys are weakly held — GC can collect them
const metadata = new WeakMap();
metadata.set(domElement, { clicks: 0 });
// When domElement is removed, metadata entry is auto-collected
// 4. Avoid accidental closures:
function createHandlers(elements) {
const hugeData = loadBigThing(); // captured by closure!
return elements.map(el => () => {
// This closure keeps hugeData alive forever
el.textContent = "clicked";
});
}
// 5. Object pooling for frequent allocations:
const pool = [];
function getObject() {
return pool.pop() || { x: 0, y: 0 };
}
function releaseObject(obj) {
obj.x = 0; obj.y = 0;
pool.push(obj);
}FAQ
Common questions about performance.