RSRobince Studio

Top 50 React interview questions and answers

The 50 questions interviewers ask most about React, with short, clear answers and code where it helps. The first 25 are the most common; the rest go deeper. Free, no sign-up.

All 50 questions

  1. What is React and why would you use it?
  2. What is JSX and how does it work?
  3. What is the difference between props and state?
  4. What is the virtual DOM and how does reconciliation work?
  5. What is the difference between functional and class components?
  6. How does the useState hook work?
  7. How does the useEffect hook work, and what is the dependency array?
  8. Why do we need keys when rendering lists?
  9. What is the difference between controlled and uncontrolled components?
  10. What is the useRef hook used for?
  11. What is the difference between useMemo and useCallback?
  12. What is React.memo and when should you use it?
  13. What is the Context API and when would you use it?
  14. What is prop drilling and how can you avoid it?
  15. What does "lifting state up" mean?
  16. How do you do conditional rendering in React?
  17. How do the lifecycle phases of a component map to hooks?
  18. What are the rules of hooks?
  19. What are custom hooks and why would you write one?
  20. What are error boundaries and how do you create one?
  21. How do you handle forms in React?
  22. How does React Router work and how do you set up basic routes?
  23. What is the difference between state updates being synchronous and asynchronous, and what is batching?
  24. What are fragments and why are they useful?
  25. What is the difference between useEffect and useLayoutEffect?
  26. What is a stale closure in React and how do you fix it?
  27. What is wrong with calling setState inside render, or inside useEffect without dependencies?
  28. When should you not use useEffect?
  29. What is useReducer and when would you choose it over useState?
  30. Why should you never mutate state directly, and how do you update objects and arrays?
  31. How do you optimize the performance of a large React list?
  32. What is code splitting, and how do React.lazy and Suspense work together?
  33. What does Suspense do for data fetching in React 18 and 19?
  34. What are concurrent features, and what do useTransition and useDeferredValue do?
  35. What are React Server Components and how do they differ from client components?
  36. What is the difference between client-side rendering, SSR, SSG and hydration?
  37. How do you fix a hydration mismatch error?
  38. What is the difference between useEffect cleanup running on every re-render versus unmount?
  39. How do you fetch data in a useEffect safely and avoid race conditions?
  40. Why does Strict Mode render components twice, and is it a bug?
  41. What is the difference between Redux, Context and Zustand for state management?
  42. How do you prevent unnecessary re-renders caused by Context?
  43. What are higher-order components and render props, and have hooks replaced them?
  44. What happens when this handler runs: does count increase by 1 or by 3?
  45. How do you test React components?
  46. What is forwardRef, and how does ref handling change in React 19?
  47. What are portals and when would you use one?
  48. How do you make a React app accessible?
  49. What is useSyncExternalStore and when do you need it?
  50. What does React Compiler do, and what changes about how you write code?

1. What is React and why would you use it?

React is a JavaScript library for building user interfaces out of reusable components. You describe what the UI should look like for a given state, and React updates the DOM efficiently when that state changes. Its benefits are a declarative style, component reuse, one-way data flow, and a large ecosystem. React itself handles only the view layer, so routing and data fetching usually come from other libraries or a framework like Next.js.

2. What is JSX and how does it work?

JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. It is not understood by browsers, so a compiler such as Babel, SWC or esbuild turns each element into a function call (jsx() in the modern runtime, React.createElement() in the classic one) that returns a plain object describing the UI. You can embed any JavaScript expression inside curly braces, and attributes use camelCase names like className and onClick.

const el = <h1 className="title">Hello, {user.name}</h1>;

3. What is the difference between props and state?

Props are inputs passed from a parent to a child and are read-only, while state is data a component owns and can change over time. Updating state with its setter triggers a re-render, whereas a child can never modify its own props directly. Use props to configure a component and state for values that change from user interaction or async results. Data flows down through props, and changes flow up through callbacks.

4. What is the virtual DOM and how does reconciliation work?

The virtual DOM is a lightweight JavaScript representation of the UI that React keeps in memory. When state changes, React renders a new tree, compares it with the previous one in a process called reconciliation, and applies only the minimal set of real DOM changes. The diffing algorithm assumes elements of different types produce different trees and uses key props to match list items. This makes updates predictable without you manipulating the DOM manually.

5. What is the difference between functional and class components?

Function components are plain functions that take props and return JSX, while class components extend React.Component and use render() and lifecycle methods. Since hooks arrived in React 16.8, function components can hold state and side effects, so they are the standard choice and are less verbose. Class components still work and are required only for error boundaries. Both can be used in the same app.

6. How does the useState hook work?

useState adds a state variable to a function component and returns the current value plus a setter function. Calling the setter schedules a re-render with the new value. State updates are batched and applied on the next render, so the variable does not change immediately after calling the setter. When the new value depends on the old one, pass an updater function.

const [count, setCount] = useState(0);
setCount(c => c + 1); // safe when based on previous value

7. How does the useEffect hook work, and what is the dependency array?

useEffect runs side effects such as data fetching, subscriptions or timers after React has rendered and committed to the screen. The dependency array tells React when to re-run it: with no array it runs after every render, with [] only after mount, and with values it runs when any of them change. The function can return a cleanup that runs before the next effect and on unmount. In development, Strict Mode runs effects twice to expose missing cleanup.

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

8. Why do we need keys when rendering lists?

Keys give each list item a stable identity so React can match items between renders and update, move or remove only the ones that changed. Without correct keys, React may reuse the wrong component instance, causing bugs such as misplaced input state. Use a unique, stable ID from your data. Avoid array indexes when the list can be reordered, filtered or have items inserted, and never use random values like Math.random().

9. What is the difference between controlled and uncontrolled components?

A controlled component has its form value held in React state and updated through onChange, so React is the single source of truth. An uncontrolled component lets the DOM keep the value, and you read it when needed through a ref or FormData. Controlled inputs make validation and conditional logic easy, while uncontrolled inputs are simpler and avoid re-rendering on every keystroke.

<input value={name} onChange={e => setName(e.target.value)} />

10. What is the useRef hook used for?

useRef returns a mutable object with a current property that persists across renders without causing a re-render when changed. It is commonly used to access DOM nodes, for example to focus an input, and to store values such as timer IDs or the previous value of a prop. Unlike state, updating ref.current does not trigger rendering, so do not use it for data that should appear in the UI.

const inputRef = useRef(null);
<input ref={inputRef} />;
inputRef.current.focus();

11. What is the difference between useMemo and useCallback?

useMemo caches the result of a calculation, while useCallback caches a function definition itself. Both recompute only when their dependencies change. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). They are performance optimizations, useful for expensive computations or for keeping stable references passed to memoized children, and should not be added everywhere by default. React Compiler can now apply much of this memoization automatically.

12. What is React.memo and when should you use it?

React.memo wraps a component so it skips re-rendering when its props are shallowly equal to the previous props. It helps when a component renders often with the same props and its rendering is expensive. It is defeated if you pass new object, array or function references on each render, which is where useMemo and useCallback come in. Measure before using it, since the comparison itself has a cost.

13. What is the Context API and when would you use it?

Context lets you pass data through the component tree without drilling props through every level. You create it with createContext, provide a value with a Provider, and read it in any descendant with useContext. It suits data that many components need, such as theme, locale or the current user. Every consumer re-renders when the value changes, so it is not ideal for rapidly changing state.

const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);

14. What is prop drilling and how can you avoid it?

Prop drilling is passing props through several intermediate components that do not use them, just to reach a deeply nested child. It makes code harder to maintain. You can avoid it with the Context API, with component composition (passing children or elements as props), or with a state management library such as Redux, Zustand or Jotai. Often simply restructuring components removes the problem.

15. 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 passes the value down as props and passes a callback so children can request changes. This keeps a single source of truth and keeps sibling components in sync, for example two inputs showing the same temperature in different units. If the lifted state is needed far away, consider context.

16. How do you do conditional rendering in React?

Since JSX is just JavaScript, you use ordinary expressions: an if statement with an early return, the ternary operator, or && for optional elements. Return null to render nothing. With &&, be careful with numbers, because 0 && <Item /> renders 0 rather than nothing, so convert to a boolean first.

{isLoggedIn ? <Dashboard /> : <Login />}
{items.length > 0 && <List items={items} />}

17. How do the lifecycle phases of a component map to hooks?

A component mounts, updates and unmounts. In classes these are componentDidMount, componentDidUpdate and componentWillUnmount. In function components, useEffect covers all three: an effect with [] runs after mount, an effect with dependencies runs after matching updates, and the cleanup function runs on unmount. Think in terms of synchronizing with data rather than reacting to lifecycle moments.

18. What are the rules of hooks?

Hooks must be called only at the top level of a function component or custom hook, never inside loops, conditions or nested functions, and only from React functions. React relies on the call order being identical on every render to match each hook with its stored state. The eslint-plugin-react-hooks lint rules enforce this. Custom hook names must start with use.

19. What are custom hooks and why would you write one?

A custom hook is a function whose name starts with use and that calls other hooks to package reusable stateful logic. It lets you share behavior, like fetching data or tracking window size, between components without changing the component hierarchy. Each call gets its own independent state. Custom hooks share logic, not state.

function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  return [on, () => setOn(v => !v)];
}

20. What are error boundaries and how do you create one?

An error boundary is a component that catches JavaScript errors in its child tree during rendering, logs them and shows a fallback UI instead of crashing the whole app. It must be a class component defining static getDerivedStateFromError or componentDidCatch, or you can use the react-error-boundary library for a function-style API. Boundaries do not catch errors in event handlers, async code or server-side rendering.

21. How do you handle forms in React?

The usual approach is controlled inputs: keep each field value in state, update it in onChange, and handle submission in onSubmit with e.preventDefault(). For many fields, use one state object or a library such as React Hook Form. React 19 also adds form Actions, where you pass a function to the form's action prop and use hooks like useActionState and useFormStatus to manage pending state.

<form onSubmit={e => { e.preventDefault(); save(values); }}>
  <input name="email" value={values.email} onChange={onChange} />
</form>

22. How does React Router work and how do you set up basic routes?

React Router maps URL paths to components without full page reloads, using the browser History API. You create a router with createBrowserRouter or wrap the app in BrowserRouter, define routes with a path and element, and navigate with Link or the useNavigate hook. Dynamic segments like /users/:id are read with useParams, and nested routes render children through Outlet.

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/users/:id" element={<User />} />
</Routes>

23. What is the difference between state updates being synchronous and asynchronous, and what is batching?

State updates in React are not applied immediately; React queues them and re-renders once with the final result. Since React 18, automatic batching groups multiple updates into a single render even inside promises, timeouts and native event handlers. Reading state right after calling the setter gives the old value. Use the updater form or an effect to work with the new value, and flushSync only in rare cases.

setCount(count + 1);
setCount(count + 1); // still +1 total
setCount(c => c + 1);
setCount(c => c + 1); // +2 total

24. What are fragments and why are they useful?

A fragment lets a component return multiple elements without adding an extra DOM node. Write it as <>...</> or <React.Fragment>, and use the long form when you need to give it a key, for example when rendering a list of groups. Fragments avoid unnecessary wrapper divs that can break layouts such as tables, flexbox or CSS grid.

25. What is the difference between useEffect and useLayoutEffect?

useEffect runs after the browser has painted, so it does not block the screen update and is right for most side effects. useLayoutEffect runs synchronously after DOM mutations but before paint, which lets you measure layout and apply changes without a visible flicker. Because it blocks painting, use it sparingly, only for things like measuring an element's size or position. It does not run on the server.

26. What is a stale closure in React and how do you fix it?

A stale closure happens when a function captures state or props from an earlier render and keeps using those old values later. It is common in setInterval callbacks or effects with missing dependencies. Fix it by listing the right dependencies, using the updater form of the setter, or storing the latest value in a ref when you need a stable callback.

useEffect(() => {
  const id = setInterval(() => setCount(c => c + 1), 1000);
  return () => clearInterval(id);
}, []); // count is never stale

27. What is wrong with calling setState inside render, or inside useEffect without dependencies?

Calling a setter unconditionally during render or in an effect with no dependency array causes an infinite loop. Each update triggers a render, which triggers another update. React detects some cases and throws "Too many re-renders". Update state only in event handlers or in effects guarded by dependencies and conditions. Derived values should be calculated during render instead of being copied into state.

28. When should you not use useEffect?

Do not use useEffect for values you can compute during render or for responding to user events. Effects are for synchronizing with external systems such as the network, timers or browser APIs. Copying props into state through an effect causes extra renders and bugs. Compute derived data directly or with useMemo, handle clicks in event handlers, and reset state by changing a key.

29. What is useReducer and when would you choose it over useState?

useReducer manages state through a pure reducer function that takes the current state and an action and returns the next state. Choose it when state has several related fields, when the next state depends on complex logic, or when many actions update the same data. It centralizes transitions, is easy to test, and pairs well with context for sharing a dispatch function.

function reducer(state, action) {
  if (action.type === 'add') return [...state, action.item];
  return state;
}
const [items, dispatch] = useReducer(reducer, []);

30. Why should you never mutate state directly, and how do you update objects and arrays?

React compares state by reference, so mutating an object or array in place leaves the same reference and React may skip the re-render. Mutation also breaks memoization and time-travel debugging. Always create a new copy with spread syntax, map, filter or slice. For deeply nested state, a helper like Immer lets you write mutating-looking code that produces immutable updates.

setTodos(todos.map(t => t.id === id ? { ...t, done: true } : t));

31. How do you optimize the performance of a large React list?

Render only what is visible with list virtualization using libraries such as react-window or TanStack Virtual. Beyond that, use stable keys, wrap row components in React.memo, avoid creating new props on every render, and paginate or infinite-scroll data. Profile with the React DevTools Profiler first so you fix a real bottleneck rather than guessing.

32. What is code splitting, and how do React.lazy and Suspense work together?

Code splitting divides your bundle into chunks loaded on demand, which shortens the initial load. React.lazy takes a function returning a dynamic import() and produces a component that loads when first rendered. While the chunk loads, the nearest Suspense boundary shows its fallback. Splitting by route is the most common and effective starting point.

const Settings = lazy(() => import('./Settings'));
<Suspense fallback={<Spinner />}><Settings /></Suspense>

33. What does Suspense do for data fetching in React 18 and 19?

Suspense lets a component declare that it is waiting for something, and shows a fallback until it is ready. For data, a component suspends while a promise is unresolved, and the boundary displays the fallback. In React 19 the use hook can read a promise directly. In practice you use it through a framework or a library such as TanStack Query rather than writing suspending promises by hand.

34. What are concurrent features, and what do useTransition and useDeferredValue do?

Concurrent rendering lets React pause, interrupt and resume rendering so urgent updates like typing stay responsive. useTransition marks a state update as non-urgent and gives you an isPending flag. useDeferredValue returns a lagging copy of a value so an expensive child renders with the old value until React has time to catch up. Neither makes slow code faster; they only prioritize work.

const [isPending, startTransition] = useTransition();
startTransition(() => setQuery(text));

35. What are React Server Components and how do they differ from client components?

Server Components render only on the server, never ship their JavaScript to the browser, and can access databases or files directly with async functions. Client Components, marked with 'use client', run in the browser and can use state, effects and event handlers. Server Components can render Client Components, but not the other way around except by passing them as children. They cut bundle size and avoid client-side data waterfalls.

36. What is the difference between client-side rendering, SSR, SSG and hydration?

Client-side rendering builds the UI in the browser from JavaScript, SSR generates HTML on the server for each request, and SSG generates it once at build time. All three send HTML or JS that must become interactive. Hydration is the step where React attaches event handlers to server-rendered HTML. SSR and SSG improve first paint and SEO, but a mismatch between server and client output causes hydration errors.

37. How do you fix a hydration mismatch error?

A hydration mismatch means the HTML rendered on the server differs from what the client renders first. Common causes are Date.now(), Math.random(), window checks or browser-only values used during render. Make the first client render match the server output, then update browser-specific values inside useEffect. For harmless differences such as timestamps, suppressHydrationWarning on that single element is acceptable.

38. What is the difference between useEffect cleanup running on every re-render versus unmount?

Cleanup runs before every re-run of the effect and on unmount, not only on unmount. If an effect depends on userId, the previous effect's cleanup runs when userId changes, before the new effect starts. This is what lets you cancel stale requests or unsubscribe from the old resource, and it is why an effect with [] only cleans up when the component leaves.

39. How do you fetch data in a useEffect safely and avoid race conditions?

A race condition occurs when an older request finishes after a newer one and overwrites the result with stale data. Guard against it with an AbortController or an ignore flag set in the cleanup function. Also handle loading and error states. In real projects, prefer TanStack Query, SWR or framework loaders, which handle caching, deduplication and cancelation for you.

useEffect(() => {
  let ignore = false;
  fetchUser(id).then(u => { if (!ignore) setUser(u); });
  return () => { ignore = true; };
}, [id]);

40. Why does Strict Mode render components twice, and is it a bug?

It is not a bug; in development, Strict Mode intentionally double-invokes render functions, state initializers and effects to surface impure code and missing cleanup. It has no effect in production. If double-running breaks your component, it usually performs side effects during render or an effect lacks a proper cleanup. Fix the underlying impurity rather than removing Strict Mode.

41. What is the difference between Redux, Context and Zustand for state management?

Context is built in and good for low-frequency global values like theme, but every consumer re-renders on change. Redux (with Redux Toolkit) offers a single store, predictable actions, middleware and strong DevTools, which suits large apps with complex shared state. Zustand is a small store with a simple hook API and selective subscriptions. Also separate server state, best handled by TanStack Query, from client state.

42. How do you prevent unnecessary re-renders caused by Context?

Every consumer of a context re-renders whenever its value changes, including when you pass a new object literal each render. Memoize the provider value with useMemo, split unrelated data into separate contexts, and separate state from dispatch. For frequently changing data, use a store with selectors such as Zustand, or useSyncExternalStore, so components subscribe only to the slice they need.

const value = useMemo(() => ({ user, logout }), [user, logout]);
<AuthContext.Provider value={value}>{children}</AuthContext.Provider>

43. What are higher-order components and render props, and have hooks replaced them?

A higher-order component is a function that takes a component and returns a new one with added behavior, while a render prop is a prop whose value is a function that returns JSX. Both share logic between components. Custom hooks have replaced most of their uses because they avoid wrapper nesting and prop naming collisions. HOCs still appear in older codebases and libraries.

44. What happens when this handler runs: does count increase by 1 or by 3?

Count increases by 1, not 3. Inside the handler, count is a snapshot from the render in which the handler was created, so all three calls set the same value. To add three, use the updater form so each call receives the latest queued value, as in setCount(c => c + 1).

function handle() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}

45. How do you test React components?

Use Vitest or Jest as the runner with React Testing Library, which renders components and queries them the way a user sees them, by role, label or text. Simulate interaction with user-event and assert on visible output rather than internal state or implementation details. Mock network calls with a tool like Mock Service Worker, and use Playwright or Cypress for end-to-end flows.

render(<Counter />);
await userEvent.click(screen.getByRole('button', { name: /add/i }));
expect(screen.getByText('1')).toBeInTheDocument();

46. What is forwardRef, and how does ref handling change in React 19?

forwardRef lets a component pass a ref received from its parent down to a DOM element or child, since ref is not a normal prop in React 18. In React 19, function components receive ref as a regular prop, so forwardRef is no longer needed and will be deprecated. Pair it with useImperativeHandle when you want to expose only specific methods to the parent.

47. What are portals and when would you use one?

A portal renders children into a DOM node outside the parent component's hierarchy, using createPortal(children, domNode). It is useful for modals, tooltips and dropdowns that must escape overflow: hidden or z-index stacking contexts. The portal content still behaves like a normal child in the React tree, so context works and events bubble up to React ancestors.

48. How do you make a React app accessible?

Start with semantic HTML such as button, nav and label rather than clickable divs. Associate labels with inputs, manage focus when opening modals or changing routes, keep everything keyboard operable, and use ARIA attributes only when native elements are not enough. Check with eslint-plugin-jsx-a11y, axe DevTools and a screen reader, and test with Testing Library's role-based queries.

49. What is useSyncExternalStore and when do you need it?

useSyncExternalStore subscribes a component to an external data source, such as a global store or browser API, in a way that is safe with concurrent rendering and avoids tearing, where parts of the UI show different values. You pass a subscribe function and a getSnapshot function. State libraries use it internally; you rarely write it directly except for custom stores or APIs like navigator.onLine.

50. What does React Compiler do, and what changes about how you write code?

React Compiler is a build-time tool that automatically memoizes components and values by analyzing your code, so you rarely need to write useMemo, useCallback or React.memo by hand. It assumes your components follow the Rules of React, being pure and not mutating props or state. Write idiomatic code, keep the lint rules on, and add manual memoization only where the compiler cannot help.

More Frontend interview questions

HTML · CSS · JavaScript · JavaScript in the Browser · TypeScript · Angular · All interview questions