Performance Optimization in React
Performance Optimization in React Interview with follow-up questions
1. What are some common performance issues in React applications?
Common React performance issues:
- Unnecessary re-renders – components re-rendering when their actual output hasn't changed (e.g. new object/array/function props each render, or context value changes affecting too many consumers).
- Expensive renders – heavy computation done during render on every update instead of being memoized or moved out.
- Large/unvirtualized lists – rendering thousands of DOM nodes instead of windowing the visible portion.
- Large bundle size – shipping too much JavaScript up front; no code-splitting/lazy loading.
- Unstable references – inline functions/objects breaking
React.memoor effect dependency arrays, causing churn. - Over-fetching / waterfalls – inefficient or sequential data fetching in effects.
- Heavy work blocking the main thread – not using concurrent features (
useTransition,useDeferredValue) to keep input responsive.
Diagnose with the React DevTools Profiler and browser performance tools before optimizing. Note the React Compiler (React 19) auto-memoizes and removes a large class of unnecessary-re-render problems without manual work.
Follow-up 1
How can you use the React DevTools Profiler to identify performance issues?
To use the React DevTools Profiler to identify performance issues:
Open the React DevTools in your browser.
Select the component you want to profile.
Click on the 'Profiler' tab.
Start recording by clicking the 'Record' button.
Interact with your application to trigger the actions or events that you want to profile.
Stop recording by clicking the 'Stop' button.
Analyze the recorded data to identify performance bottlenecks, such as components with long render times or excessive re-renders.
Use the information from the profiler to optimize your components and improve performance.
Follow-up 2
What is the impact of unnecessary re-renders on performance?
Unnecessary re-renders can have a negative impact on performance in React applications. When a component re-renders even though its props or state haven't changed, it wastes CPU cycles and can lead to performance degradation. This is because React needs to compare the previous and current versions of the component's props and state to determine if a re-render is necessary. If the comparison is not optimized or if the component is part of a large component tree, it can result in unnecessary re-renders and slow down the application.
To mitigate the impact of unnecessary re-renders, you can use techniques like shouldComponentUpdate or React.memo to optimize the rendering process and prevent unnecessary updates.
Follow-up 3
How does component size affect performance?
The size of a component can affect performance in React applications. Larger components with more complex rendering logic can take longer to render and update, leading to slower performance. This is because React needs to traverse and update the component tree, and larger components require more time and resources to do so.
To improve performance, it's important to optimize the size of components. This can be done by breaking down large components into smaller, more manageable ones, and by using techniques like code splitting and lazy loading to load components on-demand. Additionally, you can use tools like React.lazy and Suspense to asynchronously load and render components, reducing the initial load time and improving performance.
2. How can you prevent unnecessary re-renders in React?
Strategies, modern-first:
Let the React Compiler do it (React 19) – the stable compiler auto-memoizes components and values, eliminating most manual work. Often the best "optimization" is to enable it.
React.memo– wrap a function component so it skips re-rendering when its props are shallow-equal:const Row = React.memo(function Row({ item }) { ... });Stabilize props – use
useMemo(values/objects) anduseCallback(functions) so memoized children actually skip work; keep context values memoized.Right state placement – keep state as local as possible and lift only when needed, so changes re-render the smallest subtree. Split contexts so unrelated consumers don't re-render.
Keys & list hygiene – stable keys avoid remounting; virtualize long lists.
useTransition/useDeferredValue– mark non-urgent updates so expensive renders don't block input.
For legacy class components, the equivalents are shouldComponentUpdate and React.PureComponent (shallow prop/state comparison). Always profile with React DevTools before and after.
Follow-up 1
What is the role of 'shouldComponentUpdate' in preventing unnecessary re-renders?
The 'shouldComponentUpdate' lifecycle method allows you to control when a component should re-render. By default, React re-renders a component whenever its props or state change. However, you can override this behavior by implementing the 'shouldComponentUpdate' method and returning 'false' when you determine that the component doesn't need to re-render.
By carefully implementing 'shouldComponentUpdate', you can prevent unnecessary re-renders and optimize the performance of your React application.
Follow-up 2
How can PureComponent help in optimizing performance?
The 'PureComponent' class is a subclass of the regular 'Component' class in React. It provides a default implementation of the 'shouldComponentUpdate' method that performs a shallow comparison of the component's props and state.
When a component extends 'PureComponent', React automatically checks if the props or state have changed before re-rendering the component. If there are no changes, the component is not re-rendered, which can significantly improve performance.
However, it's important to note that the shallow comparison performed by 'PureComponent' may not be sufficient in some cases. If the props or state contain complex data structures, such as arrays or objects, the shallow comparison may not detect changes deep within these structures. In such cases, you may need to implement a custom 'shouldComponentUpdate' method or use 'React.memo' instead.
Follow-up 3
How does 'React.memo' function work in preventing unnecessary re-renders?
'React.memo' is a higher-order component (HOC) that can be used to wrap functional components in React. It performs a shallow comparison of the component's props, and only re-renders the component if the props have changed.
When you wrap a functional component with 'React.memo', React creates a memoized version of the component. This memoized version remembers the result of the previous render, and if the props haven't changed, it returns the memoized result instead of re-rendering the component.
By using 'React.memo', you can prevent unnecessary re-renders of functional components, similar to how 'PureComponent' works for class components. However, it's important to note that 'React.memo' only performs a shallow comparison of props, so it may not detect changes deep within complex data structures. In such cases, you may need to implement a custom comparison function using the 'second argument' of 'React.memo'.
3. What is lazy loading and how can it be implemented in React?
Lazy loading defers loading non-critical code/components until they're actually needed, shrinking the initial bundle and improving load time (code-splitting). In React you use React.lazy with Suspense:
import { lazy, Suspense } from "react";
const Dashboard = lazy(() => import("./Dashboard"));
function App() {
return (
}>
);
}
React.lazy takes a function returning a dynamic import(); the component is fetched on first render, and Suspense shows the fallback while it loads.
Common applications:
- Route-based splitting – lazy-load each route's component (the biggest win); built into Next.js and React Router v7.
- Heavy components – charts, editors, modals loaded on demand.
- Images/media – native
loading="lazy"on<img>.
Pair with an error boundary to handle chunk-load failures gracefully. Frameworks automate much of this splitting for you.
Follow-up 1
What are the benefits of lazy loading?
Lazy loading offers several benefits in React:
Improved performance: By loading components or modules only when they are needed, lazy loading reduces the initial loading time of your application.
Reduced bundle size: Lazy loading allows you to split your code into smaller chunks, which can be loaded on-demand. This can significantly reduce the size of the initial bundle that needs to be downloaded.
Better user experience: With lazy loading, your application can start rendering and become interactive faster, as non-critical components are loaded asynchronously.
Code splitting: Lazy loading enables code splitting, which means that you can split your code into smaller chunks and load them only when necessary. This can improve the maintainability and reusability of your codebase.
Follow-up 2
Can you explain how 'React.lazy' function works?
The 'React.lazy' function is a built-in function in React that allows you to lazily load components. It takes a function that returns a dynamic import, which means that the component is loaded on-demand when it is needed.
Here's an example of how to use 'React.lazy':
import React, { lazy } from 'react';
const MyLazyComponent = lazy(() => import('./MyComponent'));
function MyParentComponent() {
return (
<div>
Loading...</div>}>
);
}
In this example, the 'MyComponent' is lazily loaded when 'MyLazyComponent' is rendered. The 'Suspense' component is used to show a fallback UI while the component is being loaded.
Follow-up 3
What is Suspense in React and how is it related to lazy loading?
Suspense is a new component introduced in React 16.6 that allows you to specify a fallback UI while waiting for a component to load. It is primarily used in conjunction with lazy loading to handle the loading state of lazily loaded components.
When a component wrapped in 'Suspense' is being lazily loaded, React will display the fallback UI until the component is fully loaded and ready to be rendered. Once the component is loaded, React will replace the fallback UI with the actual component.
Here's an example of how to use 'Suspense' with lazy loading:
import React, { lazy, Suspense } from 'react';
const MyLazyComponent = lazy(() => import('./MyComponent'));
function MyParentComponent() {
return (
<div>
Loading...</div>}>
);
}
In this example, the 'Suspense' component is used to wrap the lazily loaded 'MyLazyComponent', and the fallback UI is displayed while the component is being loaded.
4. How can you optimize a React application using 'React.memo'?
React.memo is a higher-order component that memoizes a function component: it skips re-rendering when the new props are shallowly equal to the previous props.
const ListItem = React.memo(function ListItem({ item, onSelect }) {
return <li> onSelect(item.id)}>{item.name}</li>;
});
To make it effective:
- The parent must pass stable references for object/array/function props — otherwise props look "new" every render and memo never helps. Use
useMemo/useCallbackfor those props. - Optionally pass a custom comparison function as the second argument for non-shallow checks.
When to use it: a component that renders often with the same props, especially items in large lists or expensive subtrees.
When not to: cheap components, or ones whose props change every render anyway — memo then just adds overhead.
Big caveat for 2026: the React Compiler (React 19) auto-memoizes, so manual React.memo/useMemo/useCallback is increasingly unnecessary. Profile first; reach for React.memo to fix a measured re-render problem.
Follow-up 1
What is the difference between 'React.memo' and 'shouldComponentUpdate'?
'React.memo' and 'shouldComponentUpdate' are both used for optimizing React components, but they have some differences:
- 'React.memo' is used for functional components, while 'shouldComponentUpdate' is used for class components.
- 'React.memo' is a higher-order component that wraps a functional component, while 'shouldComponentUpdate' is a lifecycle method that can be implemented in a class component.
- 'React.memo' performs a shallow comparison of props to determine if the component should re-render, while 'shouldComponentUpdate' allows for more fine-grained control over the re-rendering process.
In general, 'React.memo' is recommended for optimizing functional components, while 'shouldComponentUpdate' is recommended for optimizing class components.
Follow-up 2
In what scenarios is it beneficial to use 'React.memo'?
It is beneficial to use 'React.memo' in scenarios where you have functional components that receive the same props but don't need to re-render if the props haven't changed. This can be useful in situations where the component's rendering logic is expensive or when the component is frequently re-rendered.
Some scenarios where 'React.memo' can be beneficial include:
- List items or grid items that are rendered in a loop and have the same props.
- Components that receive props from a parent component and don't rely on any internal state.
- Components that render static content or have expensive rendering logic.
By using 'React.memo' in these scenarios, you can optimize your application by reducing unnecessary re-renders and improving performance.
Follow-up 3
What are the potential downsides of using 'React.memo'?
While 'React.memo' can be a useful tool for optimizing React applications, there are some potential downsides to consider:
- 'React.memo' adds some overhead to the rendering process, as it needs to perform a shallow comparison of props. This overhead can be negligible for small components, but it can become significant for larger components or components with complex props.
- 'React.memo' may not be suitable for components that rely on reference equality for props comparison. Since 'React.memo' performs a shallow comparison, it may not detect changes in deeply nested objects or arrays.
- 'React.memo' can lead to more complex code and potential bugs if not used correctly. It's important to understand how 'React.memo' works and when it's appropriate to use it.
Overall, 'React.memo' is a powerful tool for optimizing React applications, but it should be used judiciously and with an understanding of its limitations.
5. What is virtualization and how can it be used to optimize large lists in React?
Virtualization (a.k.a. "windowing") renders only the visible portion of a large list plus a small buffer, instead of mounting every row. As the user scrolls, rows are recycled and content swapped in. This keeps the DOM small, cutting memory use and render/scroll cost dramatically for lists of thousands of items.
You typically use a library rather than building it yourself:
- TanStack Virtual – headless, framework-agnostic, the common modern choice; works for lists, grids, variable sizes.
- react-window – lightweight, fixed/variable-size lists and grids.
- react-virtualized – older, more feature-heavy.
import { useVirtualizer } from "@tanstack/react-virtual";
// render only the rows whose indices the virtualizer reports as visible
Tips:
- Provide stable keys for items.
- Account for variable row heights (measure or estimate).
- Combine with pagination/infinite scroll for very large datasets.
Use it whenever a list/table is long enough that rendering all rows hurts performance (typically hundreds-plus).
Follow-up 1
Can you explain how 'react-window' library works?
The 'react-window' library is a popular choice for virtualizing large lists in React. It provides a set of components that efficiently render only the visible items and handle scrolling and item re-rendering automatically. The main component in 'react-window' is the 'FixedSizeList' component, which renders a fixed number of items at a time and dynamically replaces off-screen items as the user scrolls. This component uses a technique called windowing, where only the visible items are rendered and the rest are rendered as placeholders. This approach allows for efficient rendering and scrolling of large lists, as it avoids the need to render and update all items at once.
Follow-up 2
What are the benefits of virtualizing large lists?
Virtualizing large lists in React offers several benefits:
Improved Performance: By rendering only the visible items, virtualization reduces the amount of DOM manipulation and re-rendering required, resulting in improved performance and faster scrolling.
Reduced Memory Usage: Virtualization allows for rendering only a subset of the list items at a time, reducing the memory footprint of the application, especially when dealing with large datasets.
Smoother User Experience: With virtualization, scrolling through large lists becomes smoother and more responsive, as only the visible items are rendered and updated, reducing the workload on the browser.
Easier Development: Virtualization libraries like 'react-window' provide abstractions and components that handle the complexities of rendering large lists, making it easier for developers to implement and maintain such functionality in their applications.
Follow-up 3
What are the potential downsides of virtualization?
While virtualization offers significant benefits for optimizing large lists, there are also some potential downsides to consider:
Complexity: Implementing virtualization in React requires understanding and working with specialized libraries or custom code. This adds complexity to the development process and may require additional learning and troubleshooting.
Increased Development Time: Virtualization may require additional time and effort to set up and configure, especially when using third-party libraries. This can impact development timelines and project schedules.
Compatibility Issues: Virtualization libraries may have compatibility issues with other React components or libraries used in the project. It is important to thoroughly test and ensure compatibility before integrating virtualization into an existing codebase.
Trade-offs in Flexibility: Virtualization techniques often involve trade-offs in terms of flexibility. For example, some virtualization libraries may limit the ability to customize the appearance or behavior of list items, as they rely on predefined rendering and scrolling logic.
Live mock interview
Mock interview: Performance Optimization in React
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.