Chapter 15 of 15
Top 75 React Interview Questions
A rapid-fire reference covering the questions most likely to come up in a real interview - organized by topic and difficulty, with explanations that go beyond a one-line definition and a runnable code example for each one, so the concept sticks. Search or filter to find what you need, and share a direct link to any question.
Showing 75 of 75 questions
The first 15 of 75 questions are free to read. Sign up free to unlock every answer and track your progress.
Unlock all 75React is a JavaScript library created by Facebook for building user interfaces, especially single-page applications. It lets developers build reusable UI components that can manage their own state, which makes complex applications easier to build. Its key features are a component-based architecture, the virtual DOM for efficient updates, a declarative style where you describe how the UI should look for a given state rather than manipulating it step by step, one-way data binding that makes state predictable, and JSX syntax that mixes markup and JavaScript in one readable structure.
Example
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
function App() {
return <Welcome name="Alice" />;
}A React Node is anything that can be rendered - a React element, a string, a number, an array, or null. A React Element is an immutable plain object describing what should appear in the UI, created via JSX or React.createElement(). A React Component is a function or class that returns a React element and encapsulates the logic and behavior behind that UI.
Example
// React Element
const element = <h1>Hello, world!</h1>;
// React Component
function Greeting() {
return <h1>Hello!</h1>;
}
// Using the component produces another element
const node = <Greeting />;JSX is a syntax extension that lets you write HTML-like markup directly inside JavaScript, which makes describing UI structure far more intuitive. Browsers cannot run JSX directly, so a tool like Babel transpiles it into ordinary React.createElement() calls before the code ever runs.
Example
const element = <div>Hello, world!</div>;
// transpiles to:
const element2 = React.createElement('div', null, 'Hello, world!');State is internal data owned and managed by a component, and it can change over time, usually in response to user interaction. Props are external data passed down from a parent component, and they are read-only from the receiving component's point of view - a component should never modify its own props.
Example
function Counter() {
const [count, setCount] = React.useState(0); // state
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function App() {
return <Counter initialCount={0} />; // props would be read here
}The key prop helps React uniquely identify each element in a list so it can efficiently determine which items changed, were added, or were removed during reconciliation, instead of re-rendering the entire list on every update.
Example
const items = [
{ id: 1, value: 'Apple' },
{ id: 2, value: 'Banana' },
];
function ItemList() {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
}Array indices are not stable identifiers - if the list is reordered, or items are added or removed anywhere but the end, the index tied to a given item changes even though the item itself didn't. React then matches the wrong DOM nodes to the wrong data during reconciliation, which can cause components to keep stale internal state or re-render more than necessary. The fix is to use a genuinely unique, stable identifier (like a database id) as the key, and only fall back to the index when the list is truly static and will never reorder.
Example
const items = ['Apple', 'Banana', 'Cherry'];
// Risky if items can be reordered, inserted, or removed
items.map((item, index) => <div key={index}>{item}</div>);A controlled component has its value driven entirely by React state - the input's value is always tied to state and updated through an onChange handler, so state is the single source of truth. An uncontrolled component instead keeps its value in the DOM itself, and you read it only when needed via a ref. Controlled components suit validation, complex forms, and dynamic updates; uncontrolled components are a simpler option for quick prototypes or when integrating with non-React code.
Example
// Controlled
function ControlledInput() {
const [value, setValue] = React.useState('');
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
// Uncontrolled
function UncontrolledInput() {
const inputRef = React.useRef();
return (
<>
<input ref={inputRef} />
<button onClick={() => alert(inputRef.current.value)}>Show Value</button>
</>
);
}Context is convenient for sharing global-ish state, but every component that consumes a context re-renders whenever that context's value changes - even if the part of the value it actually cares about didn't change. Common mistakes are putting too much unrelated state into a single context, passing a brand-new object or function as the value on every render (which defeats any memoization downstream), and reaching for context as a substitute for a real state-management solution once an app's needs grow past what a single context can cleanly handle. Memoizing the context value with useMemo and splitting contexts by concern both help keep re-renders under control.
Example
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);Hooks let functional components use state and other React features without ever writing a class, which removes the boilerplate of lifecycle methods and the confusion around "this". They make logic easier to reuse, since a custom hook can encapsulate stateful behavior and be shared across components, and they encourage a more functional style that tends to be easier to read, test, and maintain.
Example
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count is: ${count}`);
}, [count]);
return <button onClick={() => setCount(count + 1)}>Increment</button>;
}Hooks must be called at the top level of a component or custom hook - never inside loops, conditions, or nested functions - and they can only be called from React function components or from other custom hooks (whose names must start with "use"). These rules exist because React relies on the exact order hooks are called in, on every render, to correctly match each hook call back to its own piece of state; calling a hook conditionally can shift that order between renders and silently corrupt state.
Example
// Incorrect: hook called conditionally
function Bad({ someCondition }) {
if (someCondition) {
const [value, setValue] = useState(0); // breaks the call order
}
}
// Correct: hook always called, condition applied afterward
function Good({ someCondition }) {
const [value, setValue] = useState(0);
if (someCondition) {
// use value here
}
}Both hooks run side effects, but at different points relative to painting. useEffect runs asynchronously after the browser has already painted the updated UI, so it doesn't block visual updates and is the right default for data fetching, subscriptions, and most other side effects. useLayoutEffect runs synchronously right after the DOM has been updated but before the browser paints, which blocks rendering until it finishes - useful specifically for measuring or synchronizing DOM elements before the user ever sees a flash of the wrong layout, but it hurts responsiveness if overused.
Example
function Example() {
const [width, setWidth] = useState(0);
const divRef = useRef();
useLayoutEffect(() => {
setWidth(divRef.current.offsetWidth); // measured before paint
}, []);
return <div ref={divRef}>Resize me!</div>;
}setState() schedules an update rather than applying it immediately, and React can batch several calls together, so reading this.state directly inside setState can use a stale value. The callback (updater) form instead receives the guaranteed-latest prevState (and props) as arguments, so the calculation is always based on the correct value even when multiple updates happen close together. Use it any time a state update depends on the state's previous value - counters, toggles, or anything cumulative.
Example
class Counter extends React.Component {
state = { count: 0 };
increment = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }));
};
}The dependency array controls when the effect re-runs after a render. An empty array means the effect runs exactly once, after the first render. A populated array means the effect re-runs whenever any of those values changes between renders. Omitting the array entirely makes the effect run after every single render, which is rarely what you want and is a common source of performance problems and infinite loops.
Example
useEffect(() => {
const interval = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(interval);
}, []); // runs once, on mountuseRef returns a mutable { current: ... } object that persists for the entire lifetime of the component without causing a re-render when it changes. It's commonly used to reach an actual DOM node (for focusing, scrolling, or measuring), to store a mutable value like a timer id or a previous value across renders, or to hold onto anything that shouldn't itself drive the UI.
Example
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}Every render normally recreates any function defined inside a component, which means a new function reference is passed down as a prop on every render. useCallback memoizes that function so its reference only changes when its dependencies do - which matters most when the function is passed to a memoized child component, since a fresh reference would otherwise defeat that memoization and cause an unnecessary re-render.
Example
function Parent() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount((c) => c + 1), []);
return <Child onIncrement={increment} />;
}
const Child = React.memo(({ onIncrement }) => {
console.log('Child rendered');
return <button onClick={onIncrement}>Increment</button>;
});