JavaScript · Performance

Performance Interview Questions
With Answers & Code Examples

4 carefully curated Performance interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
4 questions0 beginner3 core1 advanced
Q1Core

What are debounce and throttle? When do you use each?

💡 Hint: Debounce=wait for pause; throttle=limit rate
  • Debounce — fires AFTER user stops triggering. Best for: search input, resize, form validation
  • Throttle — fires at most once per interval. Best for: scroll, mouse move
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}
const onSearch = debounce((q) => fetchResults(q), 300);
💡 Debounce = waits for storm to pass. Throttle = steady drip.
Practice this question →
Q2Core

What causes memory leaks in JavaScript and how do you detect them?

💡 Hint: Unintentional references prevent GC: forgotten timers, closures, detached DOM nodes

Memory leaks occur when objects are no longer needed but are still referenced, preventing garbage collection.

Common causes:

// 1. Forgotten intervals
const el = document.getElementById('status');
const id = setInterval(() => { el.innerHTML = Date.now(); }, 1000);
// If el is removed from DOM but interval isn't cleared → leak
// Fix: clearInterval(id) when done

// 2. Detached DOM nodes
let detached;
function create() {
  detached = document.createElement('div'); // global reference
  document.body.appendChild(detached);
  document.body.removeChild(detached); // removed from DOM...
  // but 'detached' variable still holds it → leak
}

// 3. Closures capturing large objects
function leaky() {
  const bigData = new Array(1_000_000).fill('*');
  return () => bigData[0]; // ALL of bigData kept alive!
}

// 4. Event listeners not removed
window.addEventListener('resize', heavyHandler);
// Fix: window.removeEventListener('resize', heavyHandler) on cleanup

// 5. Growing caches without eviction
const cache = {};
function store(key, val) { cache[key] = val; } // never cleared!

Detection: Chrome DevTools → Memory → Heap Snapshot → look for "Detached" DOM nodes, or compare snapshots over time for growing retained size.

💡 Use WeakMap for object-keyed caches — entries are automatically released when the key object is GC'd. Perfect for per-element data storage.
Practice this question →
Q3Core

What is the difference between reflow and repaint, and how do you avoid layout thrashing?

💡 Hint: Reflow=recalculate layout (expensive cascade); repaint=visual update only; batch DOM reads/writes

Repaint — visual property change (color, background, visibility) without affecting layout. Less expensive.

Reflow (Layout) — geometry change (width, height, position, padding, margin). Expensive: cascades through the document.

// Layout thrashing — alternating reads and writes force reflow each iteration
for (let i = 0; i < 100; i++) {
  const h = el.offsetHeight;       // READ — forces layout (reflow)
  el.style.height = h + 1 + 'px'; // WRITE — invalidates layout
}
// Browser must reflow 100 times! ❌

// Fix: batch all reads, then all writes
const h = el.offsetHeight; // single read
for (let i = 0; i < 100; i++) {
  el.style.height = (h + i) + 'px'; // writes only
}

// Best fix: CSS transforms — composited on GPU, NO reflow
el.style.transform = 'translateY(10px)'; // skip layout entirely!

// Properties that DON'T trigger reflow (GPU composited):
// transform, opacity, filter, will-change

What triggers reflow: offsetWidth/Height, getBoundingClientRect(), scrollTop, getComputedStyle(), adding/removing DOM nodes, font changes.

💡 Use requestAnimationFrame to batch DOM reads and writes in the correct phase. Libraries like FastDOM enforce this pattern.
Practice this question →
Q4Advanced

What is lazy loading and code splitting?

💡 Hint: Lazy loading: load resources only when needed; code splitting: divide bundle into smaller chunks

Lazy loading defers loading of non-critical resources until they're actually needed.

Code splitting breaks your JS bundle into smaller chunks loaded on demand.

// ─── Native Lazy Loading (images, iframes) ─────────────────
<img src="hero.jpg" loading="eager" />   // load immediately
<img src="below-fold.jpg" loading="lazy" /> // load when near viewport

// ─── JS Code Splitting (Webpack/Vite) ─────────────────────
// Static import — included in main bundle
import { utils } from './utils.js';

// Dynamic import — separate chunk, loaded on demand
async function loadChart() {
  const { Chart } = await import('./HeavyChart.js'); // separate bundle
  new Chart(element, data);
}

// Route-based splitting (React Router)
const Dashboard = React.lazy(() => import('./Dashboard'));
const Settings  = React.lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings"  element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// ─── IntersectionObserver lazy loading ────────────────────
const observer = new IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) {
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
  }
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
💡 Impact: code splitting cuts initial bundle by 30–70% for most apps. Rule of thumb: everything not needed for the landing page view should be lazily loaded.
Practice this question →

Other JavaScript Interview Topics

Core JSFunctionsAsync JSObjectsArrays'this' KeywordError HandlingModern JSDOM & EventsBrowser APIs

Ready to practice Performance?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →