Loading...
Loading...
Make your JavaScript faster. Measure, identify bottlenecks, then optimize.
Module 10 · Browser Internals & Best Practices
Production JavaScript requires more than syntax. The event loop, rendering pipeline, bundlers, debugging techniques, performance optimization, design patterns, testing, and security practices separate hobby scripts from professional software.
Learn Performance Optimization in JavaScript — covering Measure First, Runtime Performance, DOM Optimization, Loading Performance with interactive visual examples and step-by-step explanations.
Writing JavaScript that works in a tutorial is different from writing JavaScript that ships to production. The event loop explains why setTimeout(0) does not run immediately. Performance profiling finds bottlenecks before users do.
This module covers the internals and best practices that separate hobby scripts from professional software: debugging, bundlers, design patterns, testing strategies, and security fundamentals every developer should know.
Understanding performance optimization is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
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)
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);
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.
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; }
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); }
Common questions about performance optimization.