React Interview Questions and Answers
React questions asked for frontend and full-stack roles: JSX, state, hooks, effects, Context and Redux, and performance. Answers use modern React with function components and hooks.
JSX, components and props
What is React, and why is it so popular?
React is a JavaScript library for building user interfaces out of components: small, reusable functions that return what the screen should look like for the current data.
Its key ideas:
- Declarative: you describe the UI for a given state ("if loading, show a spinner"), and React updates the DOM to match. You don't write step-by-step DOM manipulation.
- Component-based: UIs are composed from independent pieces like
<Header>,<ProductCard>and<CartDrawer>. - One-way data flow: data goes down from parents to children through props, which makes behaviour predictable.
- Efficient updates: React compares the new UI description with the previous one and changes only what's different in the DOM.
It's popular because of its huge ecosystem (Next.js, React Native, TanStack Query), large job market (most frontend roles in Indian product companies and startups list React), and because the same skills carry to mobile with React Native.
React is a declarative, component-based library for building UIs. I describe what the UI should look like for the current state, and React efficiently updates the DOM to match, with data flowing one way from parent to child. It's popular because of its ecosystem, including Next.js and React Native, and its job market.
Likely follow-up: What's the difference between a library and a framework, and is React a framework?
What is JSX, and what does it compile to?
JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. Browsers can't run it directly; a compiler (Babel, SWC, esbuild) turns each tag into a function call that creates a plain JavaScript object, a React element.
const element = <h1 className="title">Hello, {name}</h1>;
// compiles (with the modern JSX transform) to roughly:
import { jsx } from "react/jsx-runtime";
const element = jsx("h1", { className: "title", children: ["Hello, ", name] });Here's the idea in plain JavaScript:
function jsx(type, props) {
return { type, props };
}
const name = "Ananya";
const element = jsx("h1", { className: "title", children: ["Hello, ", name] });
console.log(element.type);
console.log(element.props.children.join(""));h1
Hello, AnanyaKey JSX rules: use className (not class) and htmlFor; every tag must close (<img />); return a single root (or a Fragment); and anything inside { } is a JavaScript expression, not a statement.
JSX is HTML-like syntax inside JavaScript that a compiler turns into function calls, like jsx or React.createElement, which return plain objects called React elements describing the UI. Curly braces embed JavaScript expressions, and attributes use names like className and htmlFor.
Likely follow-up: Why can't you write an if statement inside JSX curly braces?
State and the useState hook
What is state, and how is it different from props?
State is data a component owns and can change over time, like the text in a search box, whether a dropdown is open, or the items in a cart. Changing state tells React to re-render the component.
- Props come from the parent and are read-only in the child.
- State is created and managed inside the component (with
useStateoruseReducer). - A parent's state often becomes a child's props.
function Counter({ step }) { // step is a prop
const [count, setCount] = useState(0); // count is state
return <button onClick={() => setCount(count + step)}>{count}</button>;
}Regular variables (let count = 0) don't work for this: they reset on every render, and changing them doesn't trigger a re-render.
State is data a component owns and changes over time, and updating it re-renders the component. Props are passed in by the parent and are read-only. A parent's state is often passed down as a child's props, and plain local variables don't work because they reset each render and don't trigger updates.
Likely follow-up: Why doesn't a regular variable work instead of useState?
How does useState work?
useState(initialValue) returns a pair: the current value and a setter function.
function SearchBox() {
const [query, setQuery] = useState("");
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}What happens:
- On the first render,
queryis""(the initial value is used only once). - Typing calls
setQuery("r"), which schedules a re-render. - React calls
SearchBoxagain; this timeuseStatereturns"r".
React knows which state belongs to which useState call by their order within the component, which is why hooks must always be called in the same order (never inside conditions or loops).
useState returns the current value and a setter. The initial value is used only on the first render; calling the setter schedules a re-render, and on that render useState returns the new value. React tracks state by the order of hook calls, so hooks can't be called conditionally.
Likely follow-up: What happens if you call useState inside an if statement?
useEffect and side effects
What is a side effect, and what is useEffect for?
A side effect is anything a component does besides calculating its JSX: fetching data, subscribing to events, setting timers, changing document.title, or talking to a non-React widget.
useEffect runs code after React has updated the screen, to synchronise your component with something outside React:
function PageTitle({ count }) {
useEffect(() => {
document.title = `Cart (${count})`;
}, [count]);
return null;
}Rendering must stay pure, so side effects go either in event handlers (when caused by a user action, like clicking Pay) or in effects (when caused by the component being shown, like connecting to a chat room while the chat is on screen).
A side effect is anything besides computing JSX, like fetching, subscriptions, timers or touching the DOM. useEffect runs after the screen updates to synchronise the component with an external system. Side effects caused by user actions belong in event handlers; effects are for synchronising because the component is displayed.
Likely follow-up: Why does useEffect run after paint rather than during render?
How does the dependency array control when an effect runs?
useEffect(() => { ... }); // after every render
useEffect(() => { ... }, []); // once after the first render (mount)
useEffect(() => { ... }, [userId]); // after mount, and whenever userId changesReact compares each dependency with its previous value using Object.is. If any changed, the effect re-runs (after running the previous cleanup).
The rule: list every prop, state value and function from the component that the effect reads. The react-hooks/exhaustive-deps ESLint rule enforces this. Leaving dependencies out to "run it only once" causes stale-value bugs; instead, restructure the code so the effect genuinely doesn't need the value.
Without an array the effect runs after every render, an empty array runs it once after mount, and listed dependencies re-run it whenever one changes by Object.is comparison. I include every reactive value the effect reads, enforced by the exhaustive-deps lint rule, rather than omitting values to control timing.
Likely follow-up: Is it safe to leave a setState function out of the dependencies?
Context, Redux and state management
What problem does the Context API solve?
Context lets a parent make a value available to every component below it, without passing props through each level. It solves prop drilling for data many components need:
- the logged-in user
- theme (light or dark)
- language or locale
- feature flags
- a shared service, like an analytics client
Context is a dependency-injection mechanism, not a full state manager. It passes a value down; you still manage the value with useState or useReducer in the provider.
Context makes a value available to every component in a subtree without passing props through each level, solving prop drilling for widely used data like the current user, theme or locale. It's a way to pass values down, not a state manager by itself, so the provider still holds the state with useState or useReducer.
Likely follow-up: When is Context the wrong tool?
How do you create and use Context?
import { createContext, useContext, useMemo, useState } from "react";
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, login: setUser, logout: () => setUser(null) }), [user]);
return <AuthContext value={value}>{children}</AuthContext>; // React 19; earlier: <AuthContext.Provider value={value}>
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used inside <AuthProvider>");
return ctx;
}
// Anywhere below the provider:
function Navbar() {
const { user, logout } = useAuth();
return user ? <button onClick={logout}>Logout {user.name}</button> : <a href="/login">Login</a>;
}Good practice:
- Export a custom hook (
useAuth) instead of the raw context, with a helpful error when it's used outside the provider. - Memoise the value object so consumers don't re-render on every provider render.
- Components read the value from the nearest provider above them; without one, they get the default from
createContext.
I create the context with createContext, wrap the tree in a provider that holds the state, and read it with useContext through a custom hook that throws a clear error outside the provider. I memoise the value object so consumers don't re-render needlessly. In React 19 the context itself can be rendered as the provider.
Likely follow-up: What value does useContext return if there's no provider above?
Performance: memo, useMemo, useCallback
What causes unnecessary re-renders?
- A parent re-renders, so all its children re-render by default, even with identical props.
- State lifted too high: typing in a search box stored in the page component re-renders the whole page.
- Context values that change often, or that are new objects every render.
- New object, array or function props created on every render, which defeat
React.memo. - Components defined inside other components, which remount on every render.
Most re-renders are cheap, so the goal isn't zero re-renders. Focus on renders that are slow (big lists, heavy calculations, charts) or frequent (on every keystroke or scroll event).
Re-renders come from the parent rendering, state kept higher than needed, frequently changing context, new object or function props that break memo, and components defined inside components. Most re-renders are cheap, so I only optimise the slow or very frequent ones, after measuring.
Likely follow-up: How do you find out which components re-render and why?
What does React.memo do?
React.memo wraps a component so it skips re-rendering when its props are shallowly equal to the previous props (each prop compared with Object.is).
const BookCard = memo(function BookCard({ book, onAdd }) {
return (
<article>
<h3>{book.title}</h3>
<button onClick={() => onAdd(book.id)}>Add to cart</button>
</article>
);
});It only helps if the props are actually stable. If the parent passes onAdd={() => addToCart(id)} or book={{ ...book }}, those are new references every render, and memo compares them as changed. That's where useCallback and useMemo come in.
A memoised component still re-renders when its own state or a context it uses changes.
React.memo makes a component skip re-rendering when its props are shallowly equal to last time. It only helps when props are stable, so inline functions or new objects from the parent defeat it, and it doesn't stop re-renders caused by the component's own state or context.
Likely follow-up: When can you pass a custom comparison function to memo?
Machine coding round practice
How should you approach a machine coding round?
Machine coding rounds (60 to 90 minutes, building a working mini-app) test whether you can ship clean, working code under time pressure. A reliable approach:
- Clarify requirements (3 to 5 minutes): which features are must-haves, what happens in edge cases (empty input, API failure), whether styling matters, and whether you can use libraries.
- Plan the components and state before coding: sketch the component tree and decide where each piece of state lives.
- Get a working version first, then improve. A complete simple app beats a half-finished perfect one.
- Handle the states interviewers check: loading, error, empty, disabled buttons, keyboard access.
- Keep code readable: small components, clear names, no giant files. Extract a custom hook if logic repeats.
- Talk while you work: explain trade-offs ("I'd debounce this in production").
- Leave 10 minutes to test edge cases and clean up.
Common evaluation points: correctness, component structure, state management, edge cases, accessibility, and code quality.
I spend the first few minutes clarifying must-have features and edge cases, sketch the components and where state lives, build a simple working version first, then handle loading, error and empty states and accessibility, explaining trade-offs as I go and saving time at the end to test and clean up.
Likely follow-up: What would you do if you're running out of time with features unfinished?
Build a counter with increment, decrement, a step size and limits of 0 to 10.
function Counter({ min = 0, max = 10 }) {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const clamp = (n) => Math.min(max, Math.max(min, n));
return (
<div>
<p aria-live="polite">Count: {count}</p>
<button onClick={() => setCount((c) => clamp(c - step))} disabled={count <= min}>−</button>
<button onClick={() => setCount((c) => clamp(c + step))} disabled={count >= max}>+</button>
<label>
Step
<input type="number" min="1" value={step} onChange={(e) => setStep(Math.max(1, Number(e.target.value) || 1))} />
</label>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}The core rule is clamping:
const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
console.log(clamp(12, 0, 10), clamp(-3, 0, 10), clamp(7, 0, 10));10 0 7Details that score points: the updater form for setting state from the previous value, disabled buttons at the limits, validating the step input, and aria-live so screen readers announce changes.
I keep count and step in state, update count with the updater form and clamp it between the limits, disable the buttons at the boundaries, validate the step input, and announce the count with aria-live for screen readers.
Likely follow-up: How would you add an "auto increment while holding the button" feature?
All 120 questions in the ebook
JSX, components and props
- What is React, and why is it so popular?
- What is JSX, and what does it compile to?
- What is the virtual DOM, and how does reconciliation work?
- Why do list items need keys, and why is using the index risky?
- What is the difference between function and class components?
- What are props, and can a component change its props?
- What is the children prop, and why is composition useful?
- What are the ways to render something conditionally?
- What are Fragments, and why use them?
- What is the difference between controlled and uncontrolled components?
- What does "lifting state up" mean?
- What is prop drilling, and how do you avoid it?
- How does event handling work in React?
- Why must React components be pure?
- Why does React prefer composition over inheritance?
- What are the common ways to style React components?
- What are React Server Components, and how do they differ from client components?
- What does StrictMode do?
- What are error boundaries?
- What are portals, and when would you use one?
State and the useState hook
- What is state, and how is it different from props?
- How does useState work?
- Why doesn't state change immediately after calling the setter?
- Why does calling setCount(count + 1) three times only add 1?
- What is automatic batching?
- Why must you treat state as immutable?
- How do you update an object in state, including nested objects?
- How do you add, remove and update items in an array in state?
- What is lazy initial state?
- When does a React component re-render?
- What is derived state, and why shouldn't you store it?
- How can you reset a component's state?
- How should you structure state?
- What is useReducer, and when would you use it instead of useState?
- How do you handle a form with many inputs?
- What is the difference between useRef and useState?
- Why shouldn't you modify state directly, like state.count = 5?
- How does React decide whether to keep or reset a component's state?
- Should you use one state object or several useState calls?
- How do you persist state to localStorage?
useEffect and side effects
- What is a side effect, and what is useEffect for?
- How does the dependency array control when an effect runs?
- What is the cleanup function, and when does it run?
- How do you fetch data in useEffect safely?
- Why does my effect run twice in development?
- What is a stale closure in React?
- What causes an infinite loop with useEffect?
- Why do objects and functions in dependency arrays cause extra effect runs?
- When should you NOT use useEffect?
- What is the difference between useEffect and useLayoutEffect?
- What are the rules of hooks, and why do they exist?
- What is a custom hook, and how do you write one?
- How do you debounce a search input?
- How do you use a ref to access a DOM element?
- How do class lifecycle methods map to hooks?
- How do you add and remove a window event listener correctly?
- Why use TanStack Query or SWR instead of fetching in useEffect?
- Should a POST request go in useEffect or in an event handler?
- What do Suspense and the use hook do?
- How do you subscribe to an external store or browser API correctly?
Context, Redux and state management
- What problem does the Context API solve?
- How do you create and use Context?
- Why can Context cause performance problems, and how do you fix them?
- When should you use Context, and when plain props?
- What are the core principles of Redux?
- Explain the Redux data flow.
- What is Redux Toolkit, and why is it the recommended way to write Redux?
- How do components read from and write to a Redux store?
- How do you handle async logic like API calls in Redux?
- Redux or Context: how do you choose?
- What is Zustand, and why is it popular?
- What is the difference between server state and client state?
- Where should form state and URL state live?
- What is Redux middleware?
- What does normalising state mean?
- How does Immer let you write "mutating" code that's actually immutable?
- What are memoised selectors, and why use them?
- How do you persist global state, and what are the pitfalls?
- How would you choose a state management approach for a new app?
- How would you build a simple global store without any library?
Performance: memo, useMemo, useCallback
- What causes unnecessary re-renders?
- What does React.memo do?
- What does useMemo do?
- What does useCallback do, and how is it different from useMemo?
- When should you NOT use memo, useMemo and useCallback?
- What is the React Compiler?
- How do you render a list of thousands of items efficiently?
- How do code splitting and lazy loading work in React?
- How do you find performance problems in a React app?
- What are useTransition and useDeferredValue?
- Why shouldn't you define components inside other components?
- How do you reduce JavaScript bundle size in a React app?
- What are SSR, SSG and hydration, and how do they affect performance?
- What are Core Web Vitals, and how does React code affect them?
- How do you avoid memory leaks in React?
- What is state colocation, and why does it help performance?
- How do you optimise images in a React or Next.js app?
- A React page feels slow. How would you investigate and fix it?
- How do you handle high-frequency events like scroll and resize?
- How do you keep the UI responsive during heavy computation?
Machine coding round practice
- How should you approach a machine coding round?
- Build a counter with increment, decrement, a step size and limits of 0 to 10.
- Build a todo list with add, toggle done, delete and a filter.
- Build a search box that fetches results as the user types, without calling the API on every keystroke.
- Fetch a list of products and show loading, error and retry states.
- Build pagination for a list of 95 items, 10 per page, with Previous and Next buttons.
- Build an accordion where only one section is open at a time.
- Build accessible tabs.
- Build a modal that closes with Escape, traps focus and returns focus afterwards.
- Build a 5-star rating component with hover preview.
- Build a 6-digit OTP input that moves focus automatically and supports pasting.
- Build a stopwatch with start, pause, reset and lap.
- Build a progress bar that fills from 0 to 100% over 5 seconds.
- Build an autocomplete with keyboard navigation.
- Build a file explorer that shows nested folders and files.
- Build infinite scroll that loads more items near the bottom.
- Build a signup form with validation for an Indian mobile number, email and PIN code.
- Build tic-tac-toe with winner and draw detection.
- Build a shopping cart that shows item totals, a discount and the final price in rupees.
- Build an image carousel with previous, next, dots and autoplay that pauses on hover.