Back to KB
Difficulty
Intermediate
Read Time
8 min

JavaScript Performance: 8 Fixes That Actually Matter (2026)

By Codcompass TeamΒ·Β·8 min read

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

How to Measure (Before You Optimize!)

// Rule #1: Never optimize without measuring first

// Browser DevTools:
// β†’ Performance tab: Record, analyze flame chart
// β†’ Network tab: Waterfall view of requests
// β†’ Memory tab: Heap snapshots, allocation timeline
// β†’ Coverage tab: See unused JS/CSS (huge quick wins!)

// Node.js:
console.time('operation');
doSomething();
console.timeEnd('operation'); // operation: 123.456ms

// More precise:
const start = process.hrtime.bigint();
doSomething();
const end = process.hrtime.bigint();
console.log(`Duration: ${Number(end - start) / 1e6}ms`);

// Professional benchmarking
const { performance, PerformanceObserver } = require('perf_hooks');

const obs = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  entries.forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
  });
});
obs.observe({ entryTypes: ['measure'] });

performance.mark('startQuery');
runDatabaseQuery();
performance.mark('endQuery');
performance.measure('query', 'startQuery', 'endQuery');

Enter fullscreen mode Exit fullscreen mode

Fix #1: Debounce & Throttle Expensive Operations

// Problem: Search input fires on every keystroke
input.addEventListener('input', (e) => {
  search(e.target.value); // Fires 50+ times per second!
});

// Solution A: Debounce β€” wait until user stops typing
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const smartSearch = debounce(search, 300); // Wait 300ms after last keystroke
input.addEventListener('input', (e) => smartSearch(e.target.value));

// Solution B: Throttle β€” run at most once per interval
function throttle(fn, interval) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= interval) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

const throttledScroll = throttle(logScrollPosition, 100); // Max once per 100ms
window.addEventListener('scroll', throttledScroll);

// When to use which?
// Debounce: search, resize calculations, form validation
// Throttle: scroll handlers, button clicks, mousemove tracking

// Modern: requestAnimationFrame for visual updates
function rafThrottle(fn) {
  let rafId = null;
  return function (...args) {
    if (rafId === null) {
      rafId = requestAnimationFrame(() => {
        fn.apply(this, args);
        rafId = null;
      });
    }
  };
}

Enter fullscreen mode Exit fullscreen mode

Fix #2: Lazy Load Everything Possible

<!-- Images: native lazy loading -->
<img src="photo.jpg" loading="lazy" alt="..." width="800" height="600" />
<!-- Always include width/height to prevent layout shift! -->

<!-- Dynamic imports (code splitting) -->
<script type="module">
  // Don't load heavy libraries until needed
  document.getElementById('chart-btn').addEventListener('click', async () => {
    const { Chart } = await import('./chart-lib.js');
    new Chart(ctx, config);
  });

  // Route-based code splitting (React/Vue do this automatically)
  const Dashboard = () => import('./Dashboard.vue');
</script>

<!-- Intersection Observer for "load when visible" -->
<div class="lazy-section" data-src="/api/section-data"></div>

<script>
const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        loadSection(entry.target);
        observer.unobserve(entry.target); // Only load once!
      }
    });
  },
  { rootMargin: '200px' } // Start loading 200px before visible
);

document.quer

πŸŽ‰ Mid-Year Sale β€” Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register β€” Start Free Trial

7-day free trial Β· Cancel anytime Β· 30-day money-back