JS Performance Optimization
Build fast, responsive JavaScript applications.
debouncethrottlelazy loadTable of Contents
- JavaScript Performance Optimization
- Why Performance Matters
- Measuring Performance
- Optimizing Loops and Iterations
- DOM Manipulation Optimization
- Memory Management
- Network Optimization
- Rendering Performance
- Code Splitting and Lazy Loading
- Caching Strategies
- Performance Tools and Profiling
- Quick Reference Checklist
- Interview Q&A
- MCQ
JavaScript Performance Optimization: A Complete Tutorial
Performance optimization helps your JavaScript run faster, load quicker, and feel smoother. This directly improves user experience and business outcomes.
1. Why Performance Matters
| Metric | Impact |
|---|---|
| 100ms delay | ~1% decrease in conversion |
| 1 second delay | ~16% decrease in customer satisfaction |
| 2 second delay | Many users abandon mobile transactions |
| 3 second load time | Large share of mobile visits are abandoned |
RAIL Model
- Response: react within ~50ms.
- Animation: target 60fps (~16ms/frame).
- Idle: use idle time for background work.
- Load: become interactive quickly.
2. Measuring Performance
Performance API
console.time("myOperation");
doExpensiveWork();
console.timeEnd("myOperation");
const start = performance.now();
doExpensiveWork();
console.log(`Operation took ${performance.now() - start}ms`);
performance.mark("start");
doExpensiveWork();
performance.mark("end");
performance.measure("Work Duration", "start", "end");Benchmark Utility
class Benchmark {
constructor(iterations = 1000) { this.iterations = iterations; this.results = []; }
run(fn, name = fn.name || "anonymous") {
const start = performance.now();
for (let i = 0; i < this.iterations; i++) fn();
const duration = performance.now() - start;
const average = duration / this.iterations;
this.results.push({ name, duration, average });
return { duration, average };
}
}FPS Meter
class FPSMeter {
constructor() { this.frames = 0; this.lastTime = performance.now(); this.isRunning = false; }
start() { this.isRunning = true; this.measure(); }
measure() {
if (!this.isRunning) return;
this.frames++;
const now = performance.now();
if (now - this.lastTime >= 1000) {
console.log(`FPS: ${Math.round((this.frames * 1000) / (now - this.lastTime))}`);
this.frames = 0; this.lastTime = now;
}
requestAnimationFrame(() => this.measure());
}
}3. Optimizing Loops and Iterations
// Cached length loop
let sum = 0;
for (let i = 0, len = largeArray.length; i < len; i++) {
sum += largeArray[i];
}
// Single-pass reduce to avoid intermediate arrays
const result = data.reduce((acc, x) => {
if (x > 10) {
const doubled = x * 2;
if (doubled < 100) acc.push(doubled.toString());
}
return acc;
}, []);Common Loop Improvements
- Cache repeated values used in each iteration.
- Avoid mutating arrays while iterating with
splice. - Use
Object.values()for object value extraction in modern runtimes.
4. DOM Manipulation Optimization
Batch Updates
function addItemsFast(items) {
const container = document.getElementById("container");
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement("div");
div.textContent = item;
fragment.appendChild(div);
});
container.appendChild(fragment);
}Avoid Layout Thrashing
// Read all first, then write all
const boxes = document.querySelectorAll(".box");
const sizes = Array.from(boxes).map(box => ({ w: box.offsetWidth, h: box.offsetHeight }));
boxes.forEach((box, i) => {
box.style.width = sizes[i].w * 2 + "px";
box.style.height = sizes[i].h * 2 + "px";
});Use requestAnimationFrame
function animate() {
element.style.transform = `translateX(${position}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);5. Memory Management
// Clear intervals/listeners when no longer needed
const timer = setInterval(() => console.log("tick"), 1000);
clearInterval(timer);
// Prefer WeakMap for element-bound cache
const elementCache = new WeakMap();
elementCache.set(document.getElementById("x"), { data: "cached" });Object Pool Pattern
class ObjectPool {
constructor(createFn, maxSize = 100) { this.createFn = createFn; this.maxSize = maxSize; this.pool = []; }
acquire() { return this.pool.pop() || this.createFn(); }
release(obj) { if (this.pool.length < this.maxSize) this.pool.push(obj); }
}6. Network Optimization
Debounce and Throttle
function debounce(func, delay) {
let timeout;
return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), delay); };
}
function throttle(func, limit) {
let inThrottle = false;
return (...args) => {
if (inThrottle) return;
func(...args); inThrottle = true;
setTimeout(() => { inThrottle = false; }, limit);
};
}Request Cache
class RequestCache {
constructor(ttl = 60000) { this.cache = new Map(); this.ttl = ttl; }
async fetch(url, options = {}) {
const key = `${url}|${JSON.stringify(options)}`;
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) return cached.data;
const data = await (await fetch(url, options)).json();
this.cache.set(key, { data, timestamp: Date.now() });
return data;
}
}7. Rendering Performance
Prefer compositor-friendly properties for animations.
// Better animation properties
element.style.transform = `translate(${x}px, ${y}px)`;
element.style.opacity = 0.9;
element.style.willChange = "transform";Avoid repeatedly reading layout properties like offsetWidth between writes.
8. Code Splitting and Lazy Loading
Dynamic Import
button.addEventListener("click", async () => {
const module = await import("heavy-chart-lib");
module.heavyChart.render(data);
});Route-Based Splitting
router.register("/dashboard", () => import("./dashboard.js"));
router.register("/profile", () => import("./profile.js"));9. Caching Strategies
LRU Cache
class LRUCache {
constructor(capacity = 100) { this.capacity = capacity; this.cache = new Map(); }
get(key) {
if (!this.cache.has(key)) return null;
const value = this.cache.get(key); this.cache.delete(key); this.cache.set(key, value);
return value;
}
set(key, value) {
if (this.cache.has(key)) this.cache.delete(key);
else if (this.cache.size >= this.capacity) this.cache.delete(this.cache.keys().next().value);
this.cache.set(key, value);
}
}Service Worker Cache (Core Idea)
self.addEventListener("install", event => {
event.waitUntil(caches.open("my-app-v1").then(cache => cache.addAll(["/", "/scripts/main.js"])));
});10. Performance Tools and Profiling
performance.mark("start-task");
await doHeavyWork();
performance.mark("end-task");
performance.measure("Task Duration", "start-task", "end-task");
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn(`Long task: ${entry.duration}ms`);
}
});
observer.observe({ entryTypes: ["longtask"] });Custom Performance Monitor
class PerformanceMonitor {
constructor() { this.metrics = new Map(); }
recordMetric(name, data) {
if (!this.metrics.has(name)) this.metrics.set(name, []);
this.metrics.get(name).push({ timestamp: Date.now(), ...data });
}
report() { console.table(Object.fromEntries(this.metrics)); }
}Quick Reference: Performance Checklist
Load Time
- Minify JS/CSS/HTML and compress assets.
- Use CDN + proper cache headers.
- Lazy load non-critical resources.
Runtime
- Clean event listeners/timers to avoid leaks.
- Batch DOM updates and use
requestAnimationFrame. - Debounce/throttle expensive handlers.
Network & Monitoring
- Batch requests and cache responses where valid.
- Track Core Web Vitals and long tasks.
- Automate performance regression checks.
Related: Asynchronous JS, Browser APIs.