React Interview Cheat Sheet
Top 50 interview questions with concise answers. Print this page or save as PDF for offline study.
1. What problem does React solve compared to directly manipulating the DOM?
Direct DOM manipulation is slow and error-prone — every change triggers browser reflows and repaints. React introduces a virtual DOM: you describe what the UI should look like, React figures out the minimal set of real DOM updates needed. Your code stays clean; React handles the expensive DOM work efficiently.
2. What is JSX and why do we use it in React?
JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. It gets compiled to React.createElement() calls. It's not required but makes component structure readable and obvious — you see the tree structure directly. JSX also catches errors at compile time that string-based templates miss.
3. Why must React components start with a capital letter?
React uses the capitalization to distinguish between built-in HTML elements (lowercase, like div or span) and custom components (capitalized, like MyButton). When React sees a lowercase tag, it renders the HTML element. When it sees uppercase, it looks for a component function or class with that name to call.
4. What is the purpose of the createRoot API introduced in React 18?
createRoot replaces ReactDOM.render() and enables all React 18 features including concurrent rendering. You call createRoot(domNode) once to set up the root, then .render( ) to display your component. The split allows React to manage multiple roots independently and enables concurrent features like Suspense and transitions.
5. What is the difference between a Component and an Element in React?
A React Element is a plain object describing what to render — like the output of JSX: { type: 'div', props: { children: 'Hello' } }. A Component is a function or class that returns elements. Components are the blueprint; elements are the instances produced each render. Elements are immutable snapshots; components are reusable factories.
6. What is the use of React Fragment?
Fragment lets you return multiple elements from a component without adding an extra DOM node. Instead of wrapping everything in a div (which adds unnecessary markup), you use or the shorthand <>...>. This keeps the DOM clean and avoids styling issues caused by extra wrapper elements.
7. Why do we use keys in lists?
Keys help React identify which items in a list changed, were added, or were removed between renders. Without keys, React can't tell items apart and may reuse the wrong DOM node — causing incorrect rendering, lost input state, and animation bugs. Keys must be stable, unique identifiers for each item.
8. What happens if we use the array index as a key?
If items are reordered or inserted, the index shifts — React sees the key change and remounts the component, losing its state. For a list of inputs, inserting a new item at the top shifts every index, causing React to treat existing items as new ones. Use a stable unique ID (like a database ID) instead.
9. What is the difference between props and state?
Props are read-only inputs passed from parent to child — the parent controls them. State is internal data owned and managed by the component itself. Props flow down; state triggers re-renders when it changes. A component can't modify its own props, but it can update its own state with a setter function.
10. Can a component modify its own props?
No. Props are read-only by design — a component receives them from its parent and must use them as-is. Modifying props would break the data flow and make components unpredictable. If a component needs to change data, it either calls a callback prop (to let the parent update state) or manages that data in its own state.
11. What does useState() return?
useState returns an array with two things: the current state value and a setter function. const [count, setCount] = useState(0) — count holds the current value, setCount triggers a re-render with the new value. The initial value (0 here) is only used on the first render.
12. Why doesn’t updating state directly trigger a re-render?
React tracks state through its setter function, not the object itself. When you do state.count = 5 directly, React never knows anything changed and doesn't schedule a re-render. You must call setState(newValue) — that's what signals React to diff the new state against the old and update the UI.
13. What is the purpose of useEffect?
useEffect lets you run side effects after a component renders — data fetching, subscriptions, DOM manipulation. It runs after the browser paints, keeping rendering fast. The dependency array controls when it re-runs. A cleanup function lets you cancel subscriptions or timers when the component unmounts.
14. What happens if you forget dependencies in useEffect?
Without all dependencies listed, the effect captures stale values from its first render (a stale closure). The effect runs but uses old data — leading to bugs that are hard to trace. ESLint's exhaustive-deps rule catches this. In React 18+, running effects twice in Strict Mode also surfaces these issues.
15. What is controlled vs uncontrolled component in forms?
Controlled: form input value is driven by React state — React owns the value, every keystroke goes through state. Uncontrolled: the DOM owns the value, accessed via a ref. Controlled gives you full control and validation at every change. Uncontrolled is simpler but you lose the ability to sync or validate on every input.
16. What is React Strict Mode and why is it useful?
Strict Mode activates extra development-only checks: it renders components twice to catch side effects in the render phase, double-fires effects to detect missing cleanup, and warns about deprecated APIs. None of this affects production. It's a great way to catch subtle bugs early without any runtime cost.
17. What is Concurrent Rendering in React 18?
Concurrent Rendering lets React interrupt, pause, and resume rendering work. Instead of blocking the main thread until a render completes, React can yield to higher-priority updates (like user input) mid-render. This makes apps feel more responsive — you can type in an input while React renders a heavy list in the background.
18. What is useId introduced in React 18?
useId generates a stable, unique ID that's consistent between server and client renders — preventing hydration mismatches. Useful for accessibility attributes like aria-labelledby where you need to link elements by ID. Unlike random IDs or incrementing counters, useId works correctly with SSR and concurrent rendering.
19. What is the difference between useEffect and useLayoutEffect?
Both run after render, but at different times. useLayoutEffect fires synchronously after DOM mutations but before the browser paints — use it when you need to measure or adjust the DOM (like reading element size) before the user sees anything. useEffect fires after painting — use it for async operations and subscriptions.
20. What are React Server Components?
React Server Components render on the server and send HTML (or serialized component tree) to the client — zero client-side JavaScript for those components. They can access databases and file systems directly, keep sensitive logic server-side, and reduce the JavaScript bundle size. Client components handle interactivity.
21. How does React reconcile two virtual DOM trees during re-rendering?
React's diffing algorithm compares two virtual DOM trees top-down. Elements of different types → full subtree replacement. Same type → compare props and update changed ones. Keys in lists → identify which items moved, were added, or removed. This O(n) heuristic is fast enough for most real-world UIs.
22. Why is the render process pure but the commit phase is not in React?
The render phase is pure — React may run it multiple times, discard results, or run it in parallel. No DOM mutations happen here. The commit phase is impure — React applies all DOM changes in one go. This two-phase design lets React interrupt rendering safely while guaranteeing that DOM updates are atomic and consistent.
23. What problem do React Server Components solve?
Server Components eliminate the need to fetch data on the client and ship that data-fetching code to the browser. Data is fetched on the server, close to the database, and only the rendered output goes to the client. This reduces bundle size, eliminates client-server waterfalls, and keeps API keys and DB connections server-side.
24. Why is data fetching in useEffect considered an anti-pattern in React 18+?
useEffect data fetching creates a client-side waterfall: render → useEffect fires → fetch → re-render. In React 18+, the preferred pattern is fetching in Server Components (zero client cost), using data-fetching libraries like SWR or React Query, or using the new use hook. useEffect for fetching is also problematic in Strict Mode's double-fire behavior.
25. What is the difference between batching in React 17 and React 18?
React 17 only batched state updates inside event handlers — updates in setTimeout or promises caused separate re-renders. React 18 introduced automatic batching: all state updates (in any context — timeouts, promises, native events) are batched together into one re-render. Fewer re-renders, better performance by default.
26. Why does React warn when state is updated during rendering?
Updating state during rendering creates an infinite loop or at least unpredictable behavior — the render triggers a state update which triggers another render. React detects this pattern and warns because the render function must be a pure, side-effect-free function. State updates belong in event handlers or effects, not mid-render.
27. What is the purpose of the useTransition hook?
useTransition marks a state update as non-urgent — it tells React "this update can be interrupted if something more important comes in." React renders the current UI immediately and updates the transition in the background. While pending, isPending is true so you can show a loading indicator. Great for search, filtering, and navigation.
28. How does React decide which components to re-render?
When state or props change, React re-renders that component and, by default, all its children — even if the children's props didn't change. React.memo, useMemo, and useCallback help break this chain by bailing out of re-renders when inputs haven't changed. Without memoization, re-renders cascade down the entire subtree.
29. Why must React state remain immutable?
React uses object identity to detect changes. If you mutate an object in place (array.push, obj.x = 1), the reference doesn't change — React thinks nothing changed and skips re-rendering. Always create new objects/arrays for state updates so React sees a new reference and knows to update the UI.
30. What happens when state setters run inside loops outside event handlers?
React 18 automatically batches multiple state updates into one re-render, even in timeouts and promises. State setters inside loops or outside event handlers are batched and applied together at the end. The component re-renders once with the final accumulated state, not once per update.
31. What is the Render Prop pattern?
Render Props pass a function as a prop — the parent component calls it with data, and the child renders whatever the function returns. It's a pattern for sharing stateful logic between components without HOCs. Largely replaced by custom hooks, which are cleaner and less nested, but still useful in some library APIs.
32. What problem does React Context solve?
Context provides a way to share values (theme, user, locale) across the component tree without prop drilling — passing props through every intermediate component. It's ideal for truly global data that many components need. For complex state management or high-frequency updates, Redux or Zustand are often better choices.
33. How does React Context differ from Redux?
Context is built into React and great for low-frequency global data (theme, auth state). Redux is a separate library with a global store, strict action-based updates, middleware support, and powerful devtools. Context re-renders all consumers on any value change; Redux lets components subscribe to specific slices.
34. What does useReducer help solve?
useReducer is better than useState when state has multiple sub-values that change together, or when the next state depends on complex logic based on the previous state. It centralizes state transition logic in one place (the reducer), making it easier to reason about, test, and debug — especially when state updates involve multiple fields.
35. Why should objects or arrays not be placed directly in dependency arrays?
Object and array literals create a new reference every render — even if the contents are the same. In a dependency array, React uses Object.is() to compare — a new reference always triggers the effect or memo. Memoize objects and arrays with useMemo, or use primitive values in dependency arrays when possible.
36. What is memoization in React and when is React.memo useful?
React.memo wraps a component and skips re-rendering if props haven't changed (shallow comparison). Useful when a child component is expensive to render and its parent re-renders frequently. Not a silver bullet — only effective when props are actually stable, and shallow comparison matches your needs.
37. What is the difference between useMemo and useCallback?
useMemo caches the result of a computation — returns the same value until dependencies change. useCallback caches a function reference — returns the same function until dependencies change. Use useMemo for expensive calculations you don't want to recompute on every render. Use useCallback to stabilize functions passed as props to memoized children.
38. Why is lifting state up used in React?
When two sibling components need to share state, move that state up to their common parent. The parent manages the state and passes it down as props. This keeps state in sync without global stores. It's the fundamental React pattern for sharing data — keep state as close to where it's needed as possible, but no lower.
39. What are controlled inputs and why can they hurt performance?
Controlled inputs re-render the component on every keystroke because each character change updates state. In large forms with many fields, this cascades re-renders across the form. Solutions: debounce state updates, split into smaller components with React.memo, or use uncontrolled inputs with refs for inputs that don't need real-time validation.
40. What is Suspense and why was it introduced?
Suspense lets components declare they're waiting for something (data, code) by throwing a Promise. React catches the throw, shows a fallback UI (like a spinner), and resumes rendering when the Promise resolves. This makes async loading declarative — you don't need loading state variables scattered through your code.
41. How does Suspense differ in data fetching vs code splitting?
For code splitting (React.lazy), Suspense catches the lazy import promise and shows a fallback while the chunk loads. For data fetching, Suspense catches a data-fetching promise thrown by the component — but this requires a Suspense-compatible data library (like SWR, React Query, or Server Components). The mechanism is the same; the integration differs.
42. What is hydration in React?
Hydration attaches React's event handlers to server-rendered HTML so the page becomes interactive. The server sends HTML (fast first paint), and the client-side React code runs and "takes over" the DOM without re-rendering everything. If the client render doesn't match the server HTML, React warns about a hydration mismatch.
43. What is partial hydration?
Partial hydration means only interactive parts of the page are hydrated — static content stays as plain HTML with no JavaScript attached. This reduces the client-side JS work needed to make the page interactive. React 18's selective hydration (in Suspense boundaries) is a form of partial hydration — interactive islands are hydrated on demand.
44. How does React handle synthetic events?
React uses a Synthetic Event system that wraps browser native events in a cross-browser compatible object. In React 17+, events are attached to the root DOM container (not document), making React embeds easier to manage alongside other libraries. Synthetic events normalize browser differences and support event delegation efficiently.
45. What is useDeferredValue used for?
useDeferredValue accepts a value and returns a deferred (potentially stale) version of it. React renders the UI with the immediate value first, then re-renders with the deferred value when it has time. Useful for search inputs — the text field updates instantly, but the expensive filtered list updates slightly later without blocking input.
46. How does React 19 improve hydration?
React 19 makes hydration more resilient — it can recover from mismatches between server HTML and client render (previously it would log a warning and re-render the entire component). It also improves streaming hydration performance and handles third-party script interference in the DOM better.
47. What is useOptimistic in React 19?
useOptimistic in React 19 lets you immediately show the expected result of an action before the server confirms it. The UI updates optimistically (appears instant), and if the server action fails, React reverts to the previous state. Replaces manual optimistic update patterns that required extra useState and error handling.
48. What is the difference between the use hook and await in React 19?
The use hook in React 19 is a new primitive that reads the value from a Promise or Context inside components (including conditionally inside loops). Unlike await which blocks, use integrates with Suspense — the component suspends while waiting, and resumes when the Promise resolves. Works in both Server and Client Components.
49. Why shouldn’t data fetching occur in constructors or render methods?
Constructors and render methods run synchronously and block painting. Fetching data there delays the initial render. It also causes bugs in concurrent mode where React may run render multiple times. Data fetching belongs in effects, server-side data loaders, or Suspense-compatible libraries that integrate with React's rendering lifecycle.
50. What is an error boundary?
Error boundaries are class components that catch JavaScript errors thrown by their children during rendering, in lifecycle methods, or in constructors. They prevent the whole app from crashing — instead, they render a fallback UI. You can't use hooks for this; class components with componentDidCatch are required.