Javascript · Performance

Performance Interview Questions
With Answers & Code Examples

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

Practice Interactively →← All Categories
9 questions0 beginner5 core4 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 is React.memo and when does it actually prevent re-renders?

💡 Hint: Shallow prop comparison — skips re-render if props are shallowly equal. Useless without stable prop references.

React.memo wraps a component and shallowly compares old and new props. Skips the re-render if props haven't changed.

const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
  // Only re-renders if items or onSelect reference changes
  return items.map(item => );
});

// ─── For memo to work, props must be stable references ────────────────────
function Parent() {
  const [count, setCount] = useState(0);

  // ❌ New array reference every render → memo useless
  const items = [{ id: 1 }, { id: 2 }];
  const onSelect = (id) => console.log(id);

  // ✅ Stable references
  const items = useMemo(() => [{ id: 1 }, { id: 2 }], []);
  const onSelect = useCallback((id) => console.log(id), []);

  return ;
}

Custom comparison:

const List = React.memo(Component, (prev, next) => {
  return prev.items.length === next.items.length; // custom equality
});
💡 React.memo is not free — it adds a comparison on every render. Only add it to components that are measurably slow or render very frequently. Profile with React DevTools Profiler first.
Practice this question →
Q3Core

What is code splitting and lazy loading in React?

💡 Hint: React.lazy + Suspense — load component code only when needed, reduces initial bundle
import { lazy, Suspense } from 'react';

// ❌ Eager import — bundled in the main chunk even if rarely used
import HeavyChart from './HeavyChart';

// ✅ Lazy — loaded only when rendered for the first time
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminPanel = lazy(() => import('./AdminPanel'));

// Suspense shows fallback while the chunk loads
function Dashboard() {
  return (
    }>
      
    
  );
}

// Route-based code splitting — most impactful
const Home    = lazy(() => import('./pages/Home'));
const Profile = lazy(() => import('./pages/Profile'));
const Admin   = lazy(() => import('./pages/Admin'));

function App() {
  return (
    }>
      
        } />
        } />
        } />
      
    
  );
}
💡 Route-level splitting is the highest-impact optimization. Admin panels, dashboards, and settings pages are perfect candidates — users on the landing page never need that code.
Practice this question →
Q4Advanced

What is virtualization (windowing) and when do you need it?

💡 Hint: Render only visible items in a long list — react-window / react-virtual renders a DOM window into a large dataset

Virtualization renders only the items visible in the viewport instead of the entire list. For 10,000 items, you might only render 20 DOM nodes at a time.

import { FixedSizeList } from 'react-window';

// ❌ Renders all 10,000 DOM nodes immediately
function NaiveList({ items }) {
  return items.map(item => );
}

// ✅ Renders ~20 DOM nodes, recycles them as you scroll
function VirtualList({ items }) {
  const Row = ({ index, style }) => (
    
{/* style positions the row */} {items[index].name}
); return ( {Row} ); }

When to virtualize:

  • Lists with 100+ items where each item has significant DOM
  • Visible performance issues (jank, slow initial render)
  • Tables, feeds, chat history, autocomplete dropdowns

Libraries: react-window (smaller), react-virtual (TanStack, headless), react-virtuoso (variable heights)

💡 Pagination or infinite scroll at ~50 items is often enough and simpler. Reach for virtualization when you genuinely have 500+ items that must all be available without pagination.
Practice this question →
Q5Advanced

What causes unnecessary re-renders and how do you diagnose them?

💡 Hint: Parent renders, new object/function props, context value changes — diagnose with React DevTools Profiler

Common causes:

  • Parent re-renders → all children re-render (even with same props, unless memoized)
  • New object/array/function created inline → reference changes → memo fails
  • Context value is a new object reference → all consumers re-render
  • State updates that produce the same value (React bails out after first redundant render)
// ❌ New object every render — breaks memo
  // new {} each time
 doWork()} />  // new fn each time
         // new [] each time

// ❌ Context providing unstable reference
function Provider({ children }) {
  const [user, setUser] = useState(null);
  return (
      {/* new object every render! */}
      {children}
    
  );
}

// ✅ Stable context value
const value = useMemo(() => ({ user, setUser }), [user]);

Diagnosis tools:

  • React DevTools → Profiler → record interaction → see what rendered and why
  • React DevTools → Components → highlight updates checkbox
  • why-did-you-render library — logs unnecessary renders with reasons
💡 Re-renders are not always a problem — React is fast. Optimize only when you measure a real performance issue. Premature memoization adds complexity and can introduce bugs.
Practice this question →
Q6Advanced

What is the React Profiler and how do you use it to diagnose performance issues?

💡 Hint: React DevTools Profiler records renders — shows which component rendered, why, and how long it took

The React DevTools Profiler helps identify slow renders and unnecessary re-renders.

Using React DevTools Profiler:

  1. Open DevTools → Profiler tab
  2. Click Record, interact with the app, stop recording
  3. See flame chart showing each render by component and duration
  4. Click a bar to see why it rendered: props changed, hook changed, parent rendered

The Profiler API for automated performance testing:

import { Profiler } from 'react';

function onRenderCallback(
  id,           // tree id (prop)
  phase,        // "mount" | "update" | "nested-update"
  actualDuration, // time spent rendering committed update
  baseDuration,   // estimated time without memoization
  startTime,
  commitTime
) {
  if (actualDuration > 16) { // over one 60fps frame
    console.warn(`Slow render in ${id}: ${actualDuration}ms`);
  }
}

function App() {
  return (
    
      
    
  );
}

What to look for:

  • Components that render when they shouldn't (add React.memo)
  • baseDuration >> actualDuration → memoization is working
  • actualDuration consistently high → expensive render, consider useMemo
💡 Profile before you optimize. Adding useMemo/useCallback everywhere without profiling first adds complexity and can actually slow things down by running extra comparisons.
Practice this question →
Q7Core

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 →
Q8Core

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 →
Q9Advanced

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

Rendering StrategiesCore JSType SystemReact FundamentalsFunctionsMicrofrontendsGenericsAsync JSHooksObjectsMonorepoArrays'this' KeywordUtility TypesError HandlingModern JSBundle OptimizationDOM & EventsState ManagementClasses & OOPCaching StrategiesComponent PatternsAdvanced TypesAuthenticationReact RouterFormsAdvanced PatternsFrontend SecurityConcurrent ReactServer ComponentsTestingEcosystemNetwork OptimizationCore Web VitalsBrowser APIs

Ready to practice Performance?

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

Start Free Practice →