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.