JS Performance Optimization

Build fast, responsive JavaScript applications.

debouncethrottlelazy load

Table of Contents

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

MetricImpact
100ms delay~1% decrease in conversion
1 second delay~16% decrease in customer satisfaction
2 second delayMany users abandon mobile transactions
3 second load timeLarge 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.

10 Performance Interview Q&A

10 Performance MCQs