DEV Community

Alex Chen
Alex Chen

Posted on

JavaScript Performance: 8 Fixes That Actually Matter (2026)

JavaScript Performance: 8 Fixes That Actually Matter (2026)

Not all optimizations are worth your time. These 8 are.

The Hierarchy of Performance

Before optimizing, ask:
1. Is anyone actually complaining about speed?
2. Have you measured where the slowdown is?
3. Is this optimization worth the complexity?

Rule of thumb: Optimize the hot path first.
Enter fullscreen mode Exit fullscreen mode

Fix #1: Debounce and Throttle User Events

The #1 performance mistake I see: running expensive operations on every keystroke/scroll.

// ❌ BAD — Runs on every keystroke
input.addEventListener('input', (e) => {
  searchAPI(e.target.value); // API call on every key press!
});

// ✅ GOOD — Waits until user stops typing
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

input.addEventListener('input', debounce((e) => {
  searchAPI(e.target.value);
}, 300));

// ✅ GOOD — Runs at most once per interval (for scroll/resize)
function throttle(fn, interval = 100) {
  let lastTime = 0;
  return (...args) => {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn(...args);
    }
  };
}

window.addEventListener('scroll', throttle(checkScrollPosition, 100));
Enter fullscreen mode Exit fullscreen mode

Impact: Can reduce function calls from thousands to dozens per second.

Fix #2: Lazy Load Images and Components

<!-- Just add loading="lazy" -->
<img src="photo.jpg" alt="..." loading="lazy" width="800" height="600">

<!-- For background images or dynamic content -->
<div data-src="heavy-image.jpg" class="lazy"></div>
Enter fullscreen mode Exit fullscreen mode
// Intersection Observer (no library needed)
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const el = entry.target;
      if (el.dataset.src) el.src = el.dataset.src;
      if (el.dataset.style) el.style.backgroundImage = `url(${el.dataset.style})`;
      el.classList.add('loaded');
      observer.unobserve(el);
    }
  });
}, { rootMargin: '100px' }); // Start loading 100px before visible

document.querySelectorAll('.lazy').forEach(el => observer.observe(el));
Enter fullscreen mode Exit fullscreen mode

Impact: Reduces initial page load by 30-70% for image-heavy pages.

Fix #3: Use requestAnimationFrame for Visual Updates

// ❌ BAD — May cause layout thrashing
function updatePosition() {
  element.style.left = calculateNewX();
  element.style.top = calculateNewY();
  requestAnimationFrame(updatePosition);
}

// ✅ GOOD — Syncs with browser's paint cycle
function updatePosition() {
  const x = calculateNewX();
  const y = calculateNewY();

  requestAnimationFrame(() => {
    element.style.transform = `translate(${x}px, ${y}px)`;
  });

  requestAnimationFrame(updatePosition);
}
Enter fullscreen mode Exit fullscreen mode

Also use requestIdleCallback for non-urgent work:

// Do heavy work when browser is idle
requestIdleCallback(deadline => {
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    processTask(tasks.shift());
  }
});
Enter fullscreen mode Exit fullscreen mode

Fix #4: Avoid Layout Thrashing

Layout thrashing = forcing browser to recalculate layout repeatedly.

// ❌ BAD — Reads/writes in loop (forces reflow each iteration)
elements.forEach(el => {
  console.log(el.offsetHeight);  // READ → forces reflow
  el.style.width = el.offsetWidth + 10 + 'px';  // WRITE → then read again
});

// ✅ GOOD — Batch reads, then batch writes
// First: all reads
const heights = elements.map(el => el.offsetHeight);

// Then: all writes
elements.forEach((el, i) => {
  el.style.width = heights[i] + 10 + 'px';
});
Enter fullscreen mode Exit fullscreen mode

Tools: Chrome DevTools → Rendering → "Paint flashing" to see repaints.

Fix #5: Efficient DOM Manipulation

// ❌ BAD — Multiple DOM insertions
const list = document.getElementById('list');
items.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item.name;
  list.appendChild(li);  // Reflow each time
});

// ✅ GOOD — Single insertion with DocumentFragment
const fragment = new DocumentFragment();
items.forEach(item => {
  const li = document.createElement('li');
  li.textContent = item.name;
  fragment.appendChild(li);
});
list.appendChild(fragment);  // Single reflow

// ✅ BETTER — innerHTML (for simple cases)
list.innerHTML = items.map(item => 
  `<li>${escapeHtml(item.name)}</li>`
).join('');
Enter fullscreen mode Exit fullscreen mode

For frequent updates, consider Virtual DOM libraries or template literals with smart diffing.

Fix #6: Optimize Loops

const arr = [/* 10,000+ items */];

// ❌ SLOW — forEach with callback overhead
arr.forEach(item => { /* ... */ });

// ❌ SLOWER — for...of (iterator protocol overhead)
for (const item of arr) { /* ... */ };

// ✅ FASTEST — Traditional for loop (for hot paths)
for (let i = 0; i < arr.length; i++) {
  const item = arr[i];
  // ...
}

// ✅ ALSO FAST — Length cached (if array might change)
for (let i = 0, len = arr.length; i < len; i++) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Reality check: For most apps, this difference is negligible. Only optimize loops that run 10,000+ times per second.

Fix #7: Memoize Expensive Computations

// Simple memoization
function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

// Usage
const expensiveCalculation = memoize((n) => {
  console.log('Computing...');
  return n * n * n; // Imagine this is slow
});

expensiveCalculation(5); // "Computing..." → 125
expensiveCalculation(5); // → 125 (from cache!)
expensiveCalculation(5); // → 125 (from cache!)

// React equivalent: useMemo / React.memo
const ExpensiveComponent = React.memo(({ data }) => {
  const result = useMemo(() => processData(data), [data]);
  return <div>{result}</div>;
});
Enter fullscreen mode Exit fullscreen mode

Fix #8: Web Workers for Heavy Computation

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ type: 'PROCESS', data: largeDataset });

worker.onmessage = (e) => {
  if (e.data.type === 'RESULT') {
    renderResults(e.data.result);
  }
};

// worker.js
self.onmessage = (e) => {
  if (e.data.type === 'PROCESS') {
    const result = heavyComputation(e.data.data);
    self.postMessage({ type: 'RESULT', result });
  }
};
Enter fullscreen mode Exit fullscreen mode

Use for: Image processing, crypto operations, large dataset analysis, real-time calculations.

What NOT To Optimize

❌ Don't optimize:
   • Code readability for marginal gains
   • One-time operations (startup, config loading)
   • Code that runs < 10ms total
   • Before measuring (you'll guess wrong 80% of the time)

✅ DO optimize:
   • Render loops (animation, scroll handlers)
   • Large list rendering (virtualize instead)
   • API response processing (big JSON payloads)
   • Event handlers that fire frequently
   • Anything users complain about being "slow"
Enter fullscreen mode Exit fullscreen mode

Measuring Performance

// Quick measurement
console.time('operation');
doSomething();
console.timeEnd('operation'); // operation: 123ms

// More precise
const start = performance.now();
doSomething();
const duration = performance.now() - start;
console.log(`Took ${duration.toFixed(2)}ms`);

// In production: Performance Observer
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 100) {
      logSlowOperation(entry.name, entry.duration);
    }
  }
});
observer.observe({ entryTypes: ['measure'] });
Enter fullscreen mode Exit fullscreen mode

What's the biggest performance win you've ever found?

Follow @armorbreak for more practical JS guides.

Top comments (0)