Skip to content

React Performance Optimization Interview Guide

Performance optimization is the process of improving the speed, responsiveness, and efficiency of a React application by minimizing unnecessary rendering, reducing bundle size, optimizing network requests, and improving memory usage.

Common causes include:

  • Parent component re-renders
  • State updates
  • Context value changes
  • New object, array, or function references created on every render
  • Props changes
<MyComponent data={{ name: "John" }} />

Even though the content is the same, a new object is created on every render, causing a re-render.

flowchart TD
    A[State or Props Change] --> B[Render Phase]
    B --> C[Virtual DOM Created]
    C --> D[Reconciliation / Diffing]
    D --> E[Commit Phase]
    E --> F[DOM Updated]

React.memo() is a Higher Order Component (HOC) that prevents unnecessary re-renders of functional components when props have not changed.

const User = React.memo(({ name }) => {
return <h1>{name}</h1>;
});

Use case: Expensive UI components.

Why Does a React.memo Component Still Re-render?

Section titled “Why Does a React.memo Component Still Re-render?”

React.memo only does a shallow prop comparison. It still re-renders when the parent passes a new object, array, or function reference on every render, even if the value looks the same:

<Child user={{ name: "John" }} />

Fix by stabilizing the reference with useMemo:

const user = useMemo(() => ({ name: "John" }), []);
<Child user={user} />
React.memo useMemo
Memoizes component rendering Memoizes computed values
Works on components Works on calculations
Prevents re-render Prevents recalculation
const result = useMemo(() => expensiveCalculation(data), [data]);

useCallback memoizes a function reference so it stays stable across renders unless its dependencies change. Useful when passing callbacks to memoized child components. See the React useCallback Interview Guide for the stale closure problem, useEffect interaction, and when it can hurt performance.

Use useMemo for expensive calculations, filtering/sorting large datasets, and complex transformations. React stores and compares dependencies, so for simple computations the memoization overhead can exceed the benefit. Rule: measure first, optimize later. See the React useMemo Interview Guide for dependency-array behavior, the React.memo interaction, and why cached values aren’t guaranteed to persist.

Reconciliation compares Virtual DOM trees and updates only changed elements.

Benefits:

  • Minimal DOM updates
  • Faster UI updates
  • Better rendering performance

Bad:

users.map((user, index) => <User key={index} />);

Good:

users.map((user) => <User key={user.id} />);

Using array indexes as keys can cause unnecessary re-renders and incorrect UI updates.

Breaking the application bundle into smaller chunks loaded on demand reduces initial load time and bundle size. See the React Code Splitting Interview Guide for React.lazy/Suspense usage, route-based splitting, dynamic imports, and the internal chunk-loading flow.

  • Code splitting
  • Tree shaking
  • Lazy loading
  • Remove unused dependencies
  • Dynamic imports
  • Production builds
Terminal window
npm run build

Render only visible items instead of the full list.

Without virtualization:

  • 100000 rows rendered

With virtualization:

  • ~20 visible rows rendered

Popular libraries:

  • react-window
  • react-virtualized

For datasets with 100k+ records:

  • Pagination
  • Virtualization
  • Server-side filtering
  • Server-side sorting
  • Memoization
  • Infinite scrolling
class User extends React.PureComponent {}

Functional equivalent:

React.memo(User);

Tools:

  • React DevTools Profiler
  • Chrome Performance Tab
  • Lighthouse
  • Web Vitals

Metrics:

  • Render time
  • Commit time
  • Component re-renders
  • Largest Contentful Paint (LCP)

Whenever a context value changes, all consumers re-render.

<UserContext.Provider value={user}>

Optimization:

const value = useMemo(() => ({ user }), [user]);

Also consider splitting contexts. See the React Context API Interview Guide for a full explanation of why context re-renders happen and why React.memo cannot prevent them.

  • Debouncing
  • Throttling
  • Caching
  • Pagination
  • Request cancellation
const debouncedSearch = debounce(searchApi, 500);

Executes an action only after the user stops typing.

debounce(searchApi, 500);

Use cases:

  • Search boxes
  • Auto-suggestions

Limits execution to once every fixed interval.

throttle(handleScroll, 200);

Use cases:

  • Scroll events
  • Resize events

Libraries like React Query and SWR replace hand-rolled useEffect data fetching with a caching layer purpose-built for server state:

  • Data caching
  • Background refetching
  • Request deduplication
  • Automatic retries

Approach:

  1. React DevTools Profiler
  2. Check object references
  3. Check callback references
  4. Apply React.memo
  5. Use useMemo/useCallback appropriately
  6. Inspect Context updates

Example scenario: A parent component updates every second (e.g., a clock tick), but a child’s data never changes. Prevent the child from re-rendering by wrapping it in React.memo and stabilizing the prop reference it receives with useMemo:

const Child = React.memo(({ user }) => {
return <div>{user.name}</div>;
});
const user = useMemo(() => ({
name: "Abirami"
}), []);

Strategies:

  • Parallel API calls with Promise.all
  • Lazy-load widgets
  • React.memo for widgets
  • useMemo for expensive computations
  • Cache API responses
  • Virtualization for large grids
  • Suspense and code splitting
  • SSR with Next.js
  • CDN caching
  • Image optimization
  • Tree shaking
  • Lazy loading
  • Bundle analysis
  • Virtual scrolling
  • Service Workers
  • API caching
  • Web Vitals monitoring
  1. State/Props change
  2. Render phase
  3. Reconciliation
  4. Diffing algorithm
  5. Commit phase
  6. DOM update

Goal:

  • Reduce renders
  • Reduce diffing
  • Reduce DOM updates
  1. Profile the application
  2. Identify expensive renders
  3. Analyze network calls
  4. Inspect bundle size
  5. Add memoization where beneficial
  6. Introduce lazy loading
  7. Virtualize large lists
  8. Measure again

Senior interviews commonly emphasize:

  • Why a page is re-rendering
  • Slow search optimization
  • Dashboard performance tuning
  • Large bundle analysis
  • Context API performance issues
  • React.memo vs useMemo vs useCallback
  • Virtual DOM vs Real DOM performance
  • Prevent unnecessary renders with React.memo, useMemo, and useCallback when appropriate.
  • Optimize loading using code splitting, lazy loading, and tree shaking.
  • Use virtualization for large datasets and pagination for scalability.
  • Profile before optimizing and validate improvements with React DevTools.
  • Senior interviews focus on real-world performance debugging rather than memorized definitions.