What is React?
React is a JavaScript library for building user interfaces out of components. You describe what the screen should look like for a given state, and React works out the smallest set of DOM changes needed to get there.
52 questions - Beginner to advanced
React interviews focus on three things: how state and props flow, what makes a component re-render, and whether you understand hooks well enough to avoid bugs. Answer with the "why", not just the API name.
React is a JavaScript library for building user interfaces out of components. You describe what the screen should look like for a given state, and React works out the smallest set of DOM changes needed to get there.
JSX is HTML-like syntax written inside JavaScript. A build tool converts each tag into a function call, so JSX is only a nicer way to describe elements. Because it becomes JavaScript, you use className instead of class and camelCase event names.
const el = <h1 className="title">Hello</h1>;
// becomes roughly:
// jsx("h1", { className: "title", children: "Hello" })
A lightweight copy of the UI kept in memory. When state changes, React builds a new copy, compares it with the old one, and applies only the differences to the real DOM. Touching the real DOM is slow, so this saves work.
The process of comparing the new element tree with the previous one. React assumes that different element types produce different trees, so it throws away the old subtree, and it uses keys to match children in a list.
Props come from the parent, are read-only inside the component, and changing them is the parent job. State belongs to the component, is private, and changing it with the setter triggers a re-render.
React compares the old and new state by reference. If you mutate the same object, the reference does not change and React sees nothing new, so the screen does not update. Always create a new object or array.
// wrong
items.push(newItem);
setItems(items);
// right
setItems([...items, newItem]);
A key tells React which item is which between renders, so it can move DOM nodes instead of rebuilding them. Use a stable unique id. Using the array index breaks when items are inserted, removed or reordered - inputs keep the wrong values and animations jump.
A form input whose value comes from React state and whose changes go back through onChange. React is the single source of truth. An uncontrolled input keeps its value in the DOM and you read it with a ref, which is simpler but harder to validate live.
const [name, setName] = useState("");
<input value={name} onChange={(e) => setName(e.target.value)} />
A wrapper that groups several elements without adding a real DOM node. Written as <>...</> or <React.Fragment>. Use the long form when you need a key, for example inside a list.
Class components use lifecycle methods and this.state. Function components are plain functions that use hooks. Function components are the standard today: less code, no this confusion, and logic is easier to share.
Deciding what to render with normal JavaScript - a ternary, && or an early return. Be careful with && and numbers: 0 && <X/> renders 0 on the screen, so compare explicitly.
{items.length > 0 && <List items={items} />} // safe
{items.length && <List items={items} />} // renders 0 when empty
Passing a prop through several components that do not need it, only to reach a deep child. Fix it with Context, composition (passing children), or a state library - but not by reaching for Context for every value.
The heart of a modern React interview.
Call hooks only at the top level of a component or another hook - never inside an if, a loop or a nested function - and only from React functions. React matches hooks to state by call order, so a conditional hook shifts everything.
It returns the current value and a setter. Calling the setter schedules a re-render. When the new value depends on the old one, pass a function so you always read the latest value.
const [count, setCount] = useState(0);
setCount((c) => c + 1); // safe inside async code and batches
Passing a function to useState so the expensive calculation runs only on the first render instead of every render.
const [rows, setRows] = useState(() => parseBigJson(raw));
It runs side effects after the render is painted - data fetching, subscriptions, timers, manual DOM work. The dependency array decides when it runs again, and the returned function cleans up before the next run and on unmount.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
No array means it runs after every render. An empty array means once after the first render. An array with values means it runs again whenever one of those values changes.
Missing dependencies, which makes the effect read stale values, and forgetting cleanup, which leaks timers and listeners. A close second is using an effect to derive state that could just be calculated during render.
useEffect runs after the browser has painted, so it does not block the screen. useLayoutEffect runs synchronously after the DOM update but before paint, which is right when you must measure an element and adjust it without the user seeing a flicker.
Two things: holding a reference to a DOM node, and keeping a mutable value between renders that does not cause a re-render when it changes - like a timer id or a previous value.
const inputRef = useRef(null);
useEffect(() => inputRef.current?.focus(), []);
<input ref={inputRef} />
Changing state re-renders the component; changing a ref does not. Use state for anything the UI shows, and a ref for values you only read in handlers and effects.
It caches the result of an expensive calculation and only recalculates when a dependency changes. It is also used to keep an object or array reference stable so a memoised child does not re-render.
const sorted = useMemo(
() => [...items].sort((a, b) => a.price - b.price),
[items]
);
It returns the same function reference between renders as long as the dependencies are unchanged. It only helps when the function is passed to a memoised child or used as an effect dependency.
When the work is cheap. They are not free - they cost memory and comparison on every render, and they make the code noisier. Measure first, then optimise.
useReducer keeps state in one place and updates it through actions. It is better when several values change together, when the next state depends heavily on the previous one, or when the update logic is complex enough to test on its own.
function reducer(state, action) {
switch (action.type) {
case "add": return { ...state, count: state.count + 1 };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
It reads a value from the nearest Provider above it, so you can share data without prop drilling. Every consumer re-renders when the provider value changes, so split contexts and memoise the value object.
const ThemeContext = createContext("light");
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Save</button>;
}
No. Context is a way to pass a value down the tree; it has no reducers, no devtools, no middleware and no selective subscriptions. It suits theme, locale and the current user. Frequently changing global data is better in a state library.
A function starting with "use" that calls other hooks. It lets you reuse logic - not markup - between components. Each component that calls it gets its own separate state.
function useDebounced(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
It generates a stable unique id that matches between the server and the client, which is the correct way to connect a label to an input in a reusable component without clashing ids.
They mark an update as low priority so React can keep the interface responsive. useTransition wraps a state update; useDeferredValue keeps showing the old value while an expensive re-render happens in the background. Both help with heavy filtered lists.
Its own state changed, a context it consumes changed, or its parent re-rendered. Note that props changing is not a separate reason - props change because the parent re-rendered.
It skips re-rendering a component when its props are shallowly equal to last time. It does nothing if you pass a new object, array or inline function on every render, which is why it pairs with useMemo and useCallback.
React groups several state updates into one re-render. Since React 18 this happens everywhere, including inside promises and timers, not just in event handlers.
Virtualise it, so only the visible rows exist in the DOM, using a library like react-window. Also memoise the row component and give it stable props and keys.
Loading part of the JavaScript only when it is needed, with React.lazy and Suspense, usually per route. It reduces the size of the first download.
const Dashboard = lazy(() => import("./Dashboard"));
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
A component that catches a JavaScript error thrown while rendering its children and shows a fallback instead of a blank page. It must be a class component today, and it does not catch errors in event handlers or async code.
In development, Strict Mode mounts a component, unmounts it and mounts it again to reveal missing cleanup. It only happens in development. If double running breaks something, your effect is missing a cleanup function.
It renders a child into a different part of the DOM, usually document.body, while keeping it in the React tree - so context and events still work. It is the standard solution for modals and tooltips that must escape an overflow: hidden parent.
createPortal(<Modal />, document.body);
Client-side rendering sends an empty page and builds it in the browser. Server-side rendering builds the HTML per request, so the first paint and SEO are better. Static site generation builds the HTML once at build time, which is fastest but only suits content that does not change per user.
The client React attaching event handlers to server-rendered HTML. If the markup the client produces does not match the server, you get a hydration mismatch warning - usually caused by using dates, random values or window during render.
Components that run only on the server and send their output to the client. They can query a database directly, send no JavaScript to the browser, and cannot use state or effects. They are combined with normal client components.
Lift the state up to their nearest common parent and pass the value down as a prop and the setter down as a callback. If the parent is far away, use context or a state library.
In a small app, useEffect with fetch plus loading and error state, and an AbortController for cleanup. In a real app, use a data library such as React Query or SWR, which handles caching, retries, deduplication and refetching for you.
useEffect(() => {
const c = new AbortController();
fetch("/api/users", { signal: c.signal })
.then((r) => r.json())
.then(setUsers)
.catch((e) => { if (e.name !== "AbortError") setError(e); });
return () => c.abort();
}, []);
The user changes the search term quickly, the first slow response arrives after the second, and the wrong results are shown. Fix it by aborting the previous request or by ignoring a response when the effect has already been cleaned up.
Server state lives somewhere else, can become stale and needs caching and refetching - that is what React Query is for. Client state is purely local, like whether a dropdown is open. Mixing them in one store makes both harder.
A router library maps the URL to a component and updates the URL with the history API instead of reloading. The server must send index.html for every path, otherwise a direct visit to a deep link returns a 404.
A wrapper component that checks whether the user is logged in and either renders the page or redirects to login, usually keeping the intended URL so the user comes back after signing in.
children is whatever you put between the opening and closing tags of a component. Passing components as children rather than passing data down many levels is often the simplest fix for prop drilling.
A function that takes a component and returns a new one with extra behaviour. It was the old way to share logic; custom hooks do the same thing more clearly, so HOCs are now mostly seen in older code.
Passing a function as a prop so the child decides what to render with the data it provides. Like HOCs, custom hooks have largely replaced this pattern.
With React Testing Library, render the component and interact with it the way a user would - find elements by their visible text or accessible role and assert on what the user sees. Avoid testing internal state or implementation details.
React is only the view library - you assemble routing, data fetching and the build yourself. Next.js is a framework built on React that adds file-based routing, server rendering, API routes and image optimisation out of the box.
CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.