Interview prep · 40 questions

React Interview Questions and Answers

This page covers the React questions asked most often in interviews, from JSX and props up to hooks, performance, error boundaries and server components. Each answer starts with a short direct reply, then explains the why with a small code example where it helps. Read the fresher section first, then try answering the later ones aloud before checking.

React interview questions: fresher level

What is React and why is it so popular?

React is a JavaScript library for building user interfaces out of small, reusable pieces called components. You describe what the UI should look like for a given state, and React updates the real DOM for you when that state changes.

  • Declarative: you write what to show, not step-by-step DOM manipulation.
  • Component-based: UI is split into reusable, testable functions.
  • One-way data flow: data moves from parent to child through props, which makes bugs easier to trace.
  • Large ecosystem: routing, state management, testing and frameworks like Next.js are built around it.

React is a library, not a full framework, so you pick extra tools for routing, data fetching and forms.

Interview tip: Interviewers often follow up with "library or framework?" — say library, and explain that it only handles the view layer.

What is JSX and how does the browser understand it?

JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. Browsers cannot run JSX directly, so a build tool (Babel, or the compiler inside Vite or similar tools) turns it into plain JavaScript function calls that create React elements.

JSX
const name = 'Priya';
const el = <h1 className="title">Hello, {name}</h1>;
// compiles to roughly:
// jsx('h1', { className: 'title', children: ['Hello, ', name] })
  • Use className instead of class and htmlFor instead of for.
  • Curly braces {} hold any JavaScript expression, but not statements like if or for.
  • A component must return a single root element (or a fragment).
  • Tags must be closed, including self-closing ones like img and input.

What is the difference between props and state?

Props are inputs passed from a parent to a child component and are read-only inside the child. State is data a component owns and manages itself, and changing it with the setter makes the component re-render.

JSX
function Counter({ step }) {          // step is a prop
  const [count, setCount] = useState(0); // count is state
  return (
    <button onClick={() => setCount(count + step)}>
      Count: {count}
    </button>
  );
}

A simple rule: if a value comes from outside and the component should not change it, it is a prop. If it changes over time because of user actions or data loading, it is state. A parent's state often becomes a child's props.

Interview tip: A common follow-up is "can a child change its props?" — no; the parent passes a callback and the child calls it.

What are functional and class components? Which one should you use?

A functional component is a plain JavaScript function that takes props and returns JSX. A class component extends React.Component, keeps state in this.state, and uses lifecycle methods like componentDidMount.

JSX
function Welcome({ name }) {
  return <p>Hi {name}</p>;
}

class WelcomeOld extends React.Component {
  render() {
    return <p>Hi {this.props.name}</p>;
  }
}

Modern React code uses functional components with hooks such as useState and useEffect. They are shorter, avoid this binding confusion, and make it easy to share logic through custom hooks. Class components still work and you will see them in older codebases, and error boundaries still need a class.

What is the virtual DOM and how does it help?

The virtual DOM is a lightweight JavaScript object tree that describes what the UI should look like. When state changes, React builds a new tree, compares it with the previous one, and applies only the differences to the real DOM.

This comparison step is called reconciliation. The real benefit is not that the virtual DOM is magically fast; it is that you can write code as if you re-render the whole UI on every change, while React works out the minimum set of real DOM updates. That keeps code simple and avoids many manual DOM bugs.

Note that "virtual DOM" is a common interview term; the React docs talk more about rendering and committing, but the idea is the same.

Interview tip: Avoid saying "the virtual DOM is always faster than the real DOM" — interviewers like to push back on that.

Why do we need keys when rendering lists in React?

Keys tell React which list item is which between renders, so it can match old and new items correctly when items are added, removed or reordered. A key must be stable and unique among siblings, usually an id from your data.

JSX
<ul>
  {todos.map(todo => (
    <li key={todo.id}>{todo.text}</li>
  ))}
</ul>

Using the array index as a key is risky when the list can change order or have items inserted. React will reuse the wrong component for the wrong item, so things like input values or local state appear on the wrong row. Index keys are acceptable only for static lists that never reorder.

Interview tip: Expect the follow-up "what happens if you use index as key?" — explain the input-value-on-wrong-row bug.

How does useState work, and why does my state not update immediately?

useState returns the current value and a setter function. Calling the setter does not change the variable right away; it schedules a re-render, and the new value is visible on the next render.

JSX
const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1);
  setCount(count + 1);
  console.log(count); // still 0 in this render
}
// After the click, count becomes 1, not 2

Both calls read the same count value from this render. When the next value depends on the previous one, use the updater form: setCount(c => c + 1). React queues these functions and applies them in order, so calling it twice adds 2.

Interview tip: This "count + 1 twice" puzzle is one of the most common React interview snippets.

What is useEffect and how does the dependency array work?

useEffect runs side effects after React has rendered and updated the screen: fetching data, setting up subscriptions or timers, or syncing with non-React code. The dependency array controls when the effect runs again.

  • No array: runs after every render.
  • Empty array []: runs after the first mount only (plus once more in development StrictMode).
  • [userId]: runs after mount and again whenever userId changes.
JSX
useEffect(() => {
  document.title = 'Cart (' + items.length + ')';
}, [items.length]);

Every value from the component that the effect reads (props, state, functions) should be listed. Leaving one out causes the effect to use stale values.

What is the difference between controlled and uncontrolled components?

In a controlled input, React state holds the value and every keystroke goes through onChange. In an uncontrolled input, the DOM keeps the value and you read it when needed, usually through a ref or the form submit event.

JSX
// Controlled
const [email, setEmail] = useState('');
<input value={email} onChange={e => setEmail(e.target.value)} />

// Uncontrolled
const inputRef = useRef(null);
<input defaultValue="" ref={inputRef} />
// later: inputRef.current.value

Controlled inputs make instant validation, formatting and conditional UI easy. Uncontrolled inputs need less code and fewer re-renders, which suits simple forms and file inputs (a file input is always uncontrolled).

Interview tip: If you set value without onChange, React warns that the field is read-only — interviewers sometimes ask why.

What does "lifting state up" mean?

Lifting state up means moving shared state to the closest common parent of the components that need it. The parent owns the state and passes the value and an update function down as props.

JSX
function Parent() {
  const [city, setCity] = useState('Pune');
  return (
    <>
      <CityPicker city={city} onChange={setCity} />
      <Weather city={city} />
    </>
  );
}

Here CityPicker and Weather both depend on city, so neither should own it. Keeping a single source of truth in the parent avoids two copies of the same data drifting out of sync.

How do you do conditional rendering in React?

Because JSX is just JavaScript, you use normal JavaScript conditions: an if before the return, the ternary operator, or the logical && operator inside JSX.

JSX
if (loading) return <Spinner />;

return (
  <div>
    {user ? <Profile user={user} /> : <LoginButton />}
    {cart.length > 0 && <CartBadge count={cart.length} />}
  </div>
);

Watch out with && and numbers: {count && ...} renders a literal 0 when count is 0, because 0 is a valid thing to render. Write count > 0 && instead. Returning null from a component renders nothing.

Interview tip: The "0 shows on screen" bug with && is a favourite trick question.

How are events handled in React?

You pass a function to a camelCase prop such as onClick or onChange. React wraps the browser event in a synthetic event object that behaves the same across browsers and has familiar methods like preventDefault() and stopPropagation().

JSX
<button onClick={handleSave}>Save</button>          // pass the function
<button onClick={() => deleteItem(id)}>Delete</button> // need an argument
<button onClick={handleSave()}>Wrong</button>       // calls it during render

The common mistake is writing onClick={handleSave()}, which runs the function on every render instead of on click. To stop a form from reloading the page, call e.preventDefault() in the submit handler.

What are React fragments and why use them?

A fragment lets a component return several elements without adding an extra wrapper element to the DOM. You write it as empty tags or as React.Fragment.

JSX
function Columns() {
  return (
    <>
      <td>Name</td>
      <td>Age</td>
    </>
  );
}

Extra wrapper divs can break layouts such as tables, flexbox and grid, and they add noise to the DOM. Use the long form React.Fragment when you need to give it a key, for example when mapping a list into groups of elements.

What is prop drilling and how can you avoid it?

Prop drilling is passing data through several layers of components that do not use it, just so a deeply nested child can receive it. It works, but it makes components noisy and refactoring painful.

  • Component composition: pass ready-made JSX as children so the middle layers do not need to know about the data.
  • Context API: provide the value once at the top and read it with useContext anywhere below.
  • State libraries like Redux Toolkit or Zustand for large, frequently changing global state.

Passing props two or three levels down is normal and fine. Reach for context only when many distant components need the same data, such as the logged-in user or the theme.

Interview tip: Mentioning composition before Context shows you know Context is not the only answer.

Why should you not mutate state directly in React?

React decides whether to re-render by comparing the old and new state with Object.is. If you mutate an array or object and pass the same reference to the setter, React sees no change and may skip the update.

JSX
// Wrong: same array reference
items.push(newItem);
setItems(items);

// Right: create a new array / object
setItems([...items, newItem]);
setUser({ ...user, name: 'Rahul' });

Treating state as immutable also keeps memoisation (React.memo, useMemo) reliable, since they rely on reference checks. For deeply nested updates, libraries like Immer let you write mutating-style code that produces new objects.

React interview questions: intermediate level

What is the cleanup function in useEffect and when does it run?

The function you return from an effect is its cleanup. React runs it before the effect runs again (when dependencies change) and when the component unmounts. Use it to undo whatever the effect set up.

JSX
useEffect(() => {
  const id = setInterval(() => setSeconds(s => s + 1), 1000);
  const onResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', onResize);

  return () => {
    clearInterval(id);
    window.removeEventListener('resize', onResize);
  };
}, []);

Without cleanup you get memory leaks, duplicate listeners, timers that keep firing after the component is gone, and setState calls on unmounted components. Subscriptions, sockets and in-flight requests should all be cleaned up.

Interview tip: Interviewers often ask you to fix a leaking setInterval — return clearInterval from the effect.

What is useRef and when would you use it?

useRef returns a mutable object { current: value } that stays the same across renders. Changing ref.current does not trigger a re-render.

  • Accessing a DOM node, for example to focus an input or measure an element.
  • Storing values that must survive renders but do not affect the UI: timer ids, previous values, an AbortController.
JSX
function Search() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return <input ref={inputRef} />;
}

If changing a value should update the screen, use state. If it should not, a ref avoids pointless renders. Do not read or write ref.current during rendering, except for lazy initialisation.

What is the difference between useMemo and useCallback?

Both cache something between renders until their dependencies change. useMemo caches the result of a calculation; useCallback caches the function itself.

JSX
const visible = useMemo(
  () => products.filter(p => p.price <= maxPrice),
  [products, maxPrice]
);

const handleAdd = useCallback(id => {
  addToCart(id);
}, [addToCart]);

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). Use useMemo for expensive calculations or to keep an object reference stable. Use useCallback when passing a function to a child wrapped in React.memo or into another hook's dependency array. Wrapping everything adds overhead and clutter, so use them where they solve a measured problem.

Interview tip: A strong answer says when not to use them, not just what they do.

What causes a component to re-render, and what does React.memo do?

A component re-renders when its own state changes, when its parent re-renders, or when a context it reads changes. A parent re-render re-renders all its children by default, even if their props did not change.

React.memo wraps a component so React skips re-rendering it when its props are shallowly equal to the previous props.

JSX
const ProductCard = React.memo(function ProductCard({ product, onAdd }) {
  return <div onClick={() => onAdd(product.id)}>{product.name}</div>;
});

The catch: if the parent creates a new object or arrow function on every render and passes it as a prop, the shallow check fails and memo does nothing. Pair it with useMemo or useCallback for those props. Memo also does not stop re-renders caused by the component's own state or context.

What is the Context API, and when would you choose Redux or another state library instead?

Context lets a parent provide a value that any component below it can read with useContext, without passing props through every level. It is a way to share data, not a full state management tool.

JSX
const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={theme}>
      <Page />
    </ThemeContext.Provider>
  );
}
// anywhere below: const theme = useContext(ThemeContext);

Context fits values that change rarely: theme, language, the logged-in user. When the provider value changes, every consumer re-renders, so large, fast-changing state can get slow. Libraries like Redux Toolkit or Zustand add selective subscriptions, devtools and structured update logic, which help in bigger apps.

Interview tip: Do not say "Context replaces Redux" — the expected answer is "it depends on update frequency and app size".

When should you use useReducer instead of useState?

Use useReducer when state has several related fields or when the next state depends on the previous one in many different ways. All update logic lives in one pure reducer function, and components just dispatch actions.

JSX
function reducer(state, action) {
  switch (action.type) {
    case 'add':    return { ...state, items: [...state.items, action.item] };
    case 'remove': return { ...state, items: state.items.filter(i => i.id !== action.id) };
    case 'clear':  return { ...state, items: [] };
    default:       return state;
  }
}
const [state, dispatch] = useReducer(reducer, { items: [] });
dispatch({ type: 'add', item: { id: 1, name: 'Pen' } });

For one or two independent values, useState is simpler. A reducer is also easy to unit-test because it is a plain function.

What is a custom hook? Can you write one?

A custom hook is a function whose name starts with use and which calls other hooks. It lets you reuse stateful logic between components. Each component that calls it gets its own independent state; the logic is shared, not the data.

JSX
function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved !== null ? JSON.parse(saved) : initial;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}
// usage: const [theme, setTheme] = useLocalStorage('theme', 'light');

The use prefix matters because linters use it to apply the rules of hooks.

Interview tip: Being able to write useFetch, useDebounce or useLocalStorage live is a very common task.

What are the rules of hooks and why do they exist?

  • Call hooks only at the top level of a component or custom hook, never inside conditions, loops, nested functions or after an early return.
  • Call hooks only from React function components or custom hooks, not from regular JavaScript functions or class components.

React does not identify hooks by name. It remembers them by the order in which they are called on each render. If a hook is skipped in one render because of an if, every hook after it gets the wrong stored state.

JSX
// Wrong
if (isLoggedIn) {
  const [name, setName] = useState('');
}

// Right: call the hook always, branch on the value
const [name, setName] = useState('');

The eslint-plugin-react-hooks package catches these mistakes and missing effect dependencies automatically.

How does reconciliation (the diffing algorithm) work in React?

Reconciliation is how React compares the new element tree with the previous one to decide what to change in the DOM. A perfect tree diff is too slow, so React uses two simple assumptions.

  • If the element type changes (say a div becomes a section, or Login becomes Signup), React throws away the old subtree, including its state, and builds a new one.
  • If the type is the same, React keeps the DOM node or component instance and only updates changed attributes and props.
  • For lists of children, React uses keys to match items between renders.

This also explains a useful trick: changing a component's key forces React to treat it as a new instance and reset its state, for example key={userId} on a profile form.

Interview tip: The "change the key to reset state" trick is a nice detail that impresses interviewers.

How do you fetch data from an API in React?

The basic pattern is to fetch inside useEffect, keep data, loading and error in state, and cancel the request in the cleanup so an old response cannot overwrite a newer one.

JSX
useEffect(() => {
  const controller = new AbortController();
  setLoading(true);
  fetch('/api/users/' + userId, { signal: controller.signal })
    .then(res => res.json())
    .then(data => setUser(data))
    .catch(err => { if (err.name !== 'AbortError') setError(err); })
    .finally(() => setLoading(false));
  return () => controller.abort();
}, [userId]);

In real projects, many teams use a data-fetching library such as TanStack Query or SWR, which adds caching, deduplication, retries and background refetching. Frameworks with server components can also fetch data on the server instead.

Interview tip: Mention race conditions and cancellation; that is what separates a good answer from a basic one.

How does routing work in a React app using React Router?

React Router maps URL paths to components on the client side, so moving between pages does not reload the whole page. You wrap the app in a router, declare routes, and navigate with Link or the useNavigate hook.

JSX
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';

function Course() {
  const { id } = useParams();
  return <h2>Course {id}</h2>;
}

<BrowserRouter>
  <Link to="/courses/7">Open course</Link>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/courses/:id" element={<Course />} />
    <Route path="*" element={<NotFound />} />
  </Routes>
</BrowserRouter>

useParams reads dynamic segments, useNavigate navigates from code (for example after login), and nested routes render inside an Outlet. Use Link instead of a plain anchor tag so navigation stays client-side.

How do you handle forms and validation in React?

For small forms, keep field values in state (controlled inputs), validate on change or on submit, and show errors from state. Always call e.preventDefault() in the submit handler.

JSX
function Signup() {
  const [form, setForm] = useState({ email: '', password: '' });
  const [error, setError] = useState('');

  const update = e => setForm({ ...form, [e.target.name]: e.target.value });

  function submit(e) {
    e.preventDefault();
    if (form.password.length < 8) return setError('Min 8 characters');
    setError('');
    // send form to API
  }
  // inputs use name, value={form.email} and onChange={update}
}

One handler using the input name attribute avoids writing a separate handler per field. For larger forms, libraries like React Hook Form (often with a schema validator such as Zod or Yup) reduce boilerplate and re-renders. Client-side validation is for user experience only; the server must validate again.

Why does my useEffect run twice in development?

In development, when your app is wrapped in StrictMode, React deliberately mounts each component, unmounts it, and mounts it again. So effects run setup, then cleanup, then setup. This does not happen in production builds.

The purpose is to expose effects that are missing a cleanup. If an effect subscribes, opens a connection or adds a listener without undoing it, the double run reveals the duplicate quickly. StrictMode also calls component functions twice during rendering to catch impure render logic.

The fix is not to remove StrictMode or add a "has run" flag, but to write a proper cleanup so that setup, cleanup, setup behaves the same as a single setup. For fetches, abort or ignore the first request in the cleanup.

Interview tip: Saying "just remove StrictMode" is the answer interviewers hope you will not give.

What is a stale closure in React hooks?

A stale closure happens when a function created during an earlier render keeps using the state or props from that render, even though newer values exist. It is common with intervals, timeouts and event listeners set up inside effects.

JSX
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1); // count is always 0 here
  }, 1000);
  return () => clearInterval(id);
}, []); // count stays at 1 forever

The effect ran once and captured count = 0. Fixes: use the updater form setCount(c => c + 1), add count to the dependency array so the effect re-subscribes with fresh values, or keep the latest value in a ref when re-subscribing is expensive.

What are higher-order components and render props? Are they still used?

Both are older patterns for sharing logic between components. A higher-order component (HOC) is a function that takes a component and returns a new component with extra props or behaviour. A render prop is a prop whose value is a function that returns JSX.

JSX
function withAuth(Component) {
  return function Protected(props) {
    const user = useUser();
    if (!user) return <Login />;
    return <Component {...props} user={user} />;
  };
}
const SecureDashboard = withAuth(Dashboard);

In modern React, custom hooks cover most of these use cases with less nesting, so new code rarely needs them. You will still meet them in older codebases and some libraries, so knowing how they work is useful.

React interview questions: advanced level

What are error boundaries and what errors do they not catch?

An error boundary is a component that catches JavaScript errors thrown while rendering its children, logs them, and shows a fallback UI instead of crashing the whole app. It must be a class component that implements static getDerivedStateFromError and/or componentDidCatch.

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

They do not catch errors in event handlers, in asynchronous code such as setTimeout or promise callbacks, during server-side rendering, or inside the boundary itself. Use try/catch for those. Many teams use the react-error-boundary package instead of writing the class by hand.

Interview tip: The list of errors it does not catch is the part most candidates miss.

How do code splitting, React.lazy and Suspense work?

React.lazy loads a component's code only when it is first rendered, using a dynamic import(). The bundler splits that component into a separate chunk, so the initial bundle is smaller. A Suspense boundary shows a fallback while the chunk downloads.

JSX
import { lazy, Suspense } from 'react';

const AdminPanel = lazy(() => import('./AdminPanel'));

function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <AdminPanel />
    </Suspense>
  );
}

The imported module must have a default export that is a component, and lazy should be called at the top level of a module, not inside a component. Route-level splitting (one chunk per page) is the most common and effective use. Suspense also works with data loading in frameworks and libraries that support it.

What are portals in React and when would you use them?

A portal renders a child into a different DOM node, outside the parent's DOM hierarchy, while keeping it in the same React tree. You create one with createPortal from react-dom.

JSX
import { createPortal } from 'react-dom';

function Modal({ children }) {
  return createPortal(
    <div className="modal">{children}</div>,
    document.body
  );
}

They are used for modals, tooltips, dropdowns and toasts that need to escape a parent with overflow: hidden or a low z-index. A key detail: React events from inside a portal still bubble up through the React tree, so a parent's onClick fires even though the DOM node lives elsewhere. Context from parents is also still available.

Interview tip: The event-bubbling-through-the-React-tree detail is the usual follow-up.

How would you find and fix performance problems in a slow React app?

Measure first with the React DevTools Profiler and the browser Performance tab to find which components render too often or take too long. Then fix the actual cause.

  • Move state down closer to where it is used, so fewer components re-render.
  • Use React.memo, useMemo and useCallback for expensive components whose props are stable.
  • Virtualise long lists (thousands of rows) so only visible rows are rendered.
  • Split code with React.lazy and route-level chunks to shrink the initial bundle.
  • Debounce search inputs and use useTransition or useDeferredValue for heavy, non-urgent updates.
  • Split a large context or memoise its value so unrelated consumers do not re-render.
  • Optimise images and avoid unnecessary dependencies in the bundle.

Interview tip: Interviewers want "profile first, then optimise" — listing memo tricks without measuring is a weak answer.

What is the difference between server components and client components?

Server components run only on the server (or at build time). Their code is not sent to the browser, they can read databases or files directly, and they cannot use state, effects or browser APIs. Client components are the classic React components that are also sent to the browser so they can use hooks and handle events.

JSX
// ProductPage.jsx (server component by default in supporting frameworks)
export default async function ProductPage({ id }) {
  const product = await db.product.find(id);
  return <AddToCart product={product} />;
}

// AddToCart.jsx
'use client';
export default function AddToCart({ product }) { /* useState, onClick... */ }

The 'use client' directive marks the boundary. Server components need a framework that supports them, such as the Next.js App Router. A plain client-only setup like a basic Vite SPA does not use them.

Interview tip: Clarify that server components are not the same as server-side rendering; SSR also renders client components to HTML.

What is the difference between useEffect and useLayoutEffect?

useEffect usually runs after the browser has painted the screen, so it does not block what the user sees. useLayoutEffect runs synchronously after React updates the DOM but before the browser paints.

Use useLayoutEffect only when you need to measure the DOM and change something before the user sees it, for example positioning a tooltip based on its measured height. With useEffect in that case, the user could see a flicker of the wrong position for one frame.

JSX
useLayoutEffect(() => {
  const { height } = tooltipRef.current.getBoundingClientRect();
  setTop(targetTop - height);
}, [targetTop]);

Because it blocks painting, overusing useLayoutEffect hurts performance. It also does nothing useful during server rendering. Default to useEffect.

What is batching of state updates in React?

Batching means React groups several state updates made in the same event into a single re-render instead of re-rendering after each one.

JSX
function handleClick() {
  setName('Asha');
  setAge(25);
  setCity('Delhi');
  // one re-render, not three
}

Older React versions batched only inside React event handlers. Modern React, when using createRoot, batches automatically everywhere: inside promises, setTimeout, native event listeners and so on. This reduces wasted renders without any code changes.

If you truly need the DOM updated immediately after one particular update, flushSync from react-dom forces it, but this is rarely needed and hurts performance.

What are useTransition and useDeferredValue used for?

Both are concurrent features that let React treat some updates as non-urgent, so typing and clicking stay responsive while heavy rendering happens in the background and can be interrupted.

JSX
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();

function onChange(e) {
  setQuery(e.target.value);            // urgent: update the input
  startTransition(() => {
    setFilter(e.target.value);         // non-urgent: filter big list
  });
}

useTransition wraps the state update you control and gives an isPending flag for a loading hint. useDeferredValue is used when you only receive a value (for example a prop) and want a lagging copy of it: const deferredQuery = useDeferredValue(query). Neither makes code faster; they change priority. Unlike debouncing, there is no fixed delay.

Why can Context cause performance problems, and how do you fix them?

Every component that reads a context re-renders whenever the provider's value changes, and React.memo on a consumer does not prevent this. A common accident is passing a new object on every render, which changes the value each time the provider's parent renders.

JSX
// New object every render: all consumers re-render
<AuthContext.Provider value={{ user, login, logout }}>

// Stable unless user changes
const value = useMemo(() => ({ user, login, logout }), [user, login, logout]);
<AuthContext.Provider value={value}>
  • Memoise the provider value (and use useCallback for functions inside it).
  • Split one large context into smaller ones, for example separate state and dispatch contexts.
  • Move fast-changing data to a library with selector-based subscriptions.

What is server-side rendering and hydration, and what causes a hydration mismatch?

With server-side rendering (SSR), React components are rendered to HTML on the server, so the user sees content quickly and search engines get real markup. Hydration is the step where React runs in the browser, attaches event handlers to that existing HTML, and makes it interactive, using hydrateRoot instead of createRoot.

Hydration expects the first client render to produce the same markup as the server. A hydration mismatch happens when it does not, for example when you render Date.now(), Math.random(), window.innerWidth or localStorage values directly during render.

The fix is to render the same output on both sides first, then read browser-only values in useEffect and update state after hydration. Frameworks like Next.js handle the SSR and hydration setup for you.

Interview tip: Expect a follow-up asking for a concrete mismatch example, such as showing the current time.