Top React Interview Questions

ReactRe-renderingPerformance Optimization

Use React.memo for pure functional components.


Use useMemo and useCallback to memoize expensive values or functions.


Key optimization: Use correct keys in lists and avoid anonymous functions in JSX when possible.
 

ReactReact.memouseMemoPerformance

React.memo: Wraps a component to prevent re-render unless props change.

 

useMemo: Caches the result of a function inside the component. Can be used for expensive calculations

const MyComponent=React.memo(({data})=>{
   return <div>this is {data}</div>
});

This will only change when data props change.

const filteredData=useMemo(()=>{
   return data.filter(item=>item.isActive);
},[data]);

For expensive calculations

ReactState ManagementComplex State

For moderately complex state, we can use multiple useState hooks.
For more complex scenarios (e.g., interdependent states), prefer useReducer or external libraries like Zustand or Redux.

ReactAuthenticationSecurity

Store tokens in localStorage or httpOnly cookies.


Use a global state (like Context or Redux) to track auth state.


Protect routes using HOCs or conditional rendering.

ReactuseEffectHooksPitfalls

Infinite loops caused by incorrect dependency arrays.


Not cleaning up, side effects properly.


Solution: Always define dependencies accurately, and clean up using the return function.

useEffect(() => {
 const timer = setInterval(doSomething, 1000);
 return () => clearInterval(timer);
}, []);

 

ReactProp DrillingState Management

Prop drilling happens when you pass data through multiple layers of components unnecessarily.
Solutions:
Use React Context API for medium complexity.


For large apps, use a state management library like Redux, Jotai, or Zustand.
 

ReactuseEffectAsynchronous OperationsHooks

Avoid updating state on unmounted components.


Use cleanup functions.

 

Cancel async operations when the component unmounts (e.g., AbortController for fetch).

useEffect(()=>{
var controller=new AbortController();
signal=controller.signal;
const fetchData=async()=>{
  const response=await fetch(url,{...options,signal});
  const result=await response.json();
};
return ()=>controller.abort;
},[]);

 

ReactPerformance DebuggingProfiling

Use React DevTools Profiler.


Identify re-renders using console logs or profiler.


Optimize render logic, split components, memoize props.
 

ReactEvent DelegationEvent Handling
  • React uses a single event listener at the root (typically on the <div id="root">),
    instead of attaching separate event listeners to each DOM element.
  • This technique is called event delegation.

 

Here's how it works step-by-step:
Single Listener:
When your React app loads, React attaches just one event listener for each event type (like click, keydown, etc.) at the root (or document level).

Event Bubbling:
When you interact with a button or any element, the event bubbles up through the DOM to the root.

Synthetic Events:
React wraps the native browser event inside its own SyntheticEvent system —
a lightweight, cross-browser wrapper — giving a consistent API across all browsers.

Dispatching:
React figures out which component the event originated from, and calls your event handler (like onClick) properly.

Efficiency:
Because React doesn't add new real DOM event listeners for each component, it's much faster and uses less memory — even if your app has thousands of elements!

ReactuseEffectuseLayoutEffectHooks
  • useEffect runs after the DOM has been painted to the screen.
    It's asynchronous and does not block the browser from showing the UI.
  • useLayoutEffect runs before the DOM is painted.
    It runs synchronously after the DOM updates but before the screen updates.
    It blocks the browser from painting until the code inside finishes.

 

When to Use it?

useEffect ? when you don't need to block the browser (default).

useLayoutEffect ? when you need to measure or manipulate DOM immediately before the user sees anything.

 

Always prefer useEffect first.
Only use useLayoutEffect if you notice a flicker or need to measure/adjust layout before paint.

Because useLayoutEffect can block rendering and cause performance issues if used unnecessarily.

ReactControlled ComponentsUncontrolled ComponentsForms

Controlled components are those whose form data is handled by the React component's state. 

Uncontrolled components store their data in the DOM itself.

Controlled Example:

function ControlledInput() {
 const [value, setValue] = useState('');
 return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}

Uncontrolled Example:

function UncontrolledInput() {
 const inputRef = useRef(null);
 const handleClick = () => alert(inputRef.current.value);
 return <>
   <input ref={inputRef} />
   <button onClick={handleClick}>Show Value</button>
 </>;
}

ReactError HandlingComponents

ReactAuthenticationSecurity

 Using error boundaries, we can handle the errors in react.

Example:

class ErrorBoundary extends React.Component {
 state = { hasError: false };
 static getDerivedStateFromError(error) {
   return { hasError: true };
 }
 componentDidCatch(error, info) {
   // Log error
 }
 render() {
   if (this.state.hasError) return <h1>Something went wrong.</h1>;
   return this.props.children;
 }
}

 

ReactPerformance OptimizationRendering

Use React.memo to prevent unnecessary re-renders.


Use useMemo and useCallback for expensive calculations or function definitions.


Lazy load components with React.lazy.


Use code splitting with dynamic import().

ReactProp DrillingState Management

Prop drilling is passing data through many nested components. To avoid it we can use the following.

 

  • Use Context API
  • Use Redux or Recoil
  • Lift state up only when necessary

ReactAuthenticationSecurity

 Use Context API or a global store like Redux to store the auth state, then use ProtectedRoute components to guard routes.

Example:

function ProtectedRoute({ children }) {
 const auth = useContext(AuthContext);
 return auth.isAuthenticated ? children : <Navigate to="/login" />;
}

ReactReconciliationRendering

Reconciliation is the process by which React updates the DOM. When state changes, React builds a virtual DOM tree and compares it with the previous tree (diffing). Only the changes are applied to the real DOM.

 

How does it work?

  • When something changes (like a button click or data update), React re-renders the component virtually (in memory, not directly on the screen).
  • Then React compares the new Virtual DOM to the previous Virtual DOM.
  • It calculates the minimal set of changes (called "diffing") needed to update the real DOM.
  • React then efficiently updates the real DOM, applying only the parts that changed.
  • This comparison and updating process is called reconciliation.

ReactDOM ManipulationjQueryComparison

React is declarative and component-based, using a Virtual DOM for faster updates, while jQuery is imperative and manually updates the real DOM directly. React scales better for large apps because it abstracts away manual DOM handling.

ReactKey PropList Rendering

Keys help React identify which items changed, are added, or removed, and enhance performance by minimizing DOM operations.

 

It helps React track which items changed, added, or removed during re-rendering.

With keys, React can efficiently update only the items that actually changed — instead of re-rendering the entire list.

 

Without a proper key, React cannot reliably identify

  • Which list items are new,
  • Which are deleted,
  • Which are reordered.

This can cause

  • Performance problems (more DOM updates than necessary),
  • Bugs like wrong component state preserved.

ReactReact FiberRendering

React Fiber is a reimplementation of React's core reconciliation algorithm, introduced in React 16. It aims to improve performance, especially for complex applications, by enabling incremental rendering. Instead of performing updates in one go, Fiber breaks down rendering into smaller, manageable units of work. This allows React to pause, prioritize, and resume work as needed, leading to smoother user experiences.
                                  
The following are the key features of React fiber.

Incremental Rendering:

It splits rendering work into chunks, allowing React to pause and resume, ensuring smoother UIs.

Prioritization:

Fiber enables React to prioritize updates, giving precedence to urgent tasks like user interactions.

Concurrency:

It lays the groundwork for concurrent mode, allowing React to handle multiple updates simultaneously without blocking the main thread.

Work Loop:

Fiber introduces a work loop that processes units of work in a cooperative scheduling manner, allowing pausing and resuming work.

 

React Fiber represents the nodes of the DOM tree as fiber nodes, linked together to form a fiber tree. This structure mirrors the component tree and facilitates efficient updates.

ReactCustom HookscomponentDidMount
import { useEffect } from 'react';
function useComponentDidMount(callback) {
 useEffect(() => {
   callback();
   // Empty dependency array ensures this runs once
 }, []);
}
// Usage
useComponentDidMount(() => {
 console.log('Component mounted');
});

ReactCustom HooksDebouncing
function useDebounce(value, delay) {
 const [debounced, setDebounced] = useState(value);
 useEffect(() => {
   const handler = setTimeout(() => setDebounced(value), delay);
   return () => clearTimeout(handler);
 }, [value, delay]);
 return debounced;
}

  Usage:

const debouncedSearch = useDebounce(searchQuery, 500);

This can be used in search boxes, filters, and resizing to reduce function calls.

ReactMemory LeaksPerformance

React itself doesn't inherently prevent memory leaks, but it provides tools and patterns to manage component lifecycles effectively, which can help avoid them. Memory leaks in React applications typically occur when resources are not properly released when components unmount.


To prevent memory leaks, we should follow the best practices below.
Use the Cleanup Function in useEffect:
This function allows you to execute code when a component unmounts, which can be used to clear timers, unsubscribe from events, and cancel API requests.
Clear Timers and Intervals:
Always clear timers and intervals using clearTimeout and clearInterval within the cleanup function of useEffect or in the componentWillUnmount lifecycle method (for class components).
Cancel API Requests:
Use an AbortController to cancel ongoing API requests when a component unmounts.
Remove Event Listeners:
Remove event listeners using removeEventListener within the cleanup function of useEffect or in the componentWillUnmount lifecycle method.
Avoid Global References:
Be mindful of storing references in global objects or contexts that are not cleaned up properly.
Use Strict Mode:
Enable strict mode in your JavaScript code to help catch potential issues that could lead to memory leaks.

 

Example:

useEffect(() => {
 const controller = new AbortController();
 fetch('url', { signal: controller.signal });
 return () => controller.abort(); // Cleanup
}, []);

ReactBatchingState Updates

React batching is a mechanism that groups multiple state updates into a single re-render to optimize performance and user experience. This reduces unnecessary DOM manipulations, leading to smoother UI updates and faster loading times. React 18 introduced automatic batching, which extends batching to various scenarios like setTimeout, promises, and native event handlers, further enhancing performance. 


What it does:
React's batching mechanism prevents individual state updates from triggering immediate re-renders. 
Instead, it groups these updates and applies them as a single batch, ensuring only one re-render occurs for all grouped updates. 
This optimization is particularly useful in event handlers and lifecycle methods where multiple state changes might occur quickly. 
Why it's important:


Improved performance:
By reducing the number of re-renders, React applications become more efficient and responsive. 
Smoother UI:
Fewer DOM manipulations lead to a more fluid and less choppy user experience. 
Simplified development:
Developers can write more intuitive code without having to manually optimize for batching. 

ReactConcurrent ModeRendering

Concurrent Mode in React is a set of new features that help React apps stay responsive even when rendering large components or handling complex updates. It allows React to interrupt rendering tasks, work on high-priority updates first, and resume or abandon less critical tasks as needed. This makes the user interface feel smoother and more responsive, especially during time-consuming rendering operations.


Key Features and Concepts
Interruptible Rendering:
React can pause rendering to handle other events, like user input, ensuring the UI remains responsive.
Prioritizing Updates:
React can prioritize updates, processing important ones first and deferring less urgent ones.
Time Slicing:
Rendering work is broken into smaller chunks, allowing React to pause and resume rendering without blocking the main thread.
Suspense:
Components can "suspend" rendering while waiting for data, displaying a fallback UI until the data is ready.
Transitions:
Help differentiate between urgent and non-urgent updates, ensuring the UI remains responsive during transitions.
Automatic Batching:
Multiple state updates are grouped into a single re-render, improving performance. 


Benefits of concurrent mode
Improved User Experience:
Smoother and more responsive UI, especially during complex rendering tasks.
Better Performance:
Efficient handling of updates and rendering, leading to faster initial render times.
Enhanced Error Handling:
Improved error boundaries for handling errors during rendering, suspending, or resuming components. 


How it Works?
Concurrent Mode introduces a new rendering mechanism that allows React to work on multiple tasks concurrently. It does not mean that state updates are executed in parallel, but rather that they can overlap, with React prioritizing tasks based on their urgency.

 

import React, { useState, useTransition } from 'react';
function MyComponent() {
 const [isPending, startTransition] = useTransition();
 const [count, setCount] = useState(0);
 const handleClick = () => {
   startTransition(() => {
     setCount(c => c + 1);
   });
 };
 return (
   <div>
     {isPending && <p>Updating...</p>}
     <p>Count: {count}</p>
     <button onClick={handleClick}>Increment</button>
   </div>
 );
}

In this example, useTransition is used to mark the state update as non-urgent. If other urgent updates occur while this update is being processed, React can prioritize them, ensuring the UI remains responsive.

ReactSuspenseLazy Loading
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./HeavyComponent'));
function App() {
 return (
   <Suspense fallback={<div>Loading...</div>}>
     <LazyComponent />
   </Suspense>
 );
}

It improves initial load time. Use when components are large or used conditionally.

ReactState ManagementLarge Applications

For large apps, we can use

  1. Context API for global state
  2. State management libraries like Redux or Zustand
  3. Split state logically by domain
     

ReactState UpdatesRendering

 React throws an error:
 "Rendered more hooks than during the previous render."
You should not update state inside render because it causes infinite re-renders:

 

 Wrong:

const MyComponent = () => {
  const [val, setVal] = useState(0);
  setVal(1); // Bad
  return <div>{val}</div>;
};

 Instead of this, we should use useEffect.

ReactuseImperativeHandleHooks

useImperativeHandle is a React Hook that lets you customize the handle exposed as a ref.

 

import { useImperativeHandle, forwardRef, useRef } from 'react';
const Input = forwardRef((props, ref) => {
 const inputRef = useRef();
 useImperativeHandle(ref, () => ({
   focus: () => inputRef.current.focus()
 }));
 return <input ref={inputRef} />;
});
function Parent() {
 const inputRef = useRef();
 return <>
   <Input ref={inputRef} />
   <button onClick={() => inputRef.current.focus()}>Focus</button>
 </>;
}

 Used when Parent needs to call child’s methods (imperative control).

ReactCustom HooksEvent Handling
function useOnClickOutside(ref, handler) {
 useEffect(() => {
   const listener = (e) => {
     if (!ref.current || ref.current.contains(e.target)) return;
     handler(e);
   };
   document.addEventListener("mousedown", listener);
   return () => document.removeEventListener("mousedown", listener);
 }, [ref, handler]);
}

 It can be used with dropdowns, modals, tooltips, etc.