StackInterview logoStackInterview icon

Explore

Library

Resources

Articles

Insights

StackInterview

StackInterview helps developers prepare for full-stack interviews with structured questions, real company interview insights, and modern technology coverage.

About UsFAQContactPrivacy PolicyTerms of Service

© 2026 StackInterview. Built for engineers, by engineers.

Developed and Maintained by Abhijeet Kushwaha

All Articles
⚛️React & Frontend25 min read

Top 50 React Interview Questions and Answers (2026)

React is used by 40.6% of all developers. Master all 50 interview questions - hooks, Virtual DOM, Server Components, and performance patterns - asked at top product companies in 2026.

React is the most-used frontend library for five consecutive years, relied on by 40.6% of professional developers. This guide covers all 50 interview questions junior and mid-level React developers face at product companies - fundamentals, hooks, patterns, performance, Router, and modern React 18/19 features - each with a concise answer and a runnable code snippet.

reactinterview-questionsfrontendreact-hooksreact-2026coding-interview
On this page
  1. Top 50 React Interview Questions and Answers (2026)
  2. Section 1 - React Fundamentals (Q1–Q10)
  3. Q1. What is React? How is it different from a full framework?
  4. Q2. What is JSX and how does the browser understand it?
  5. Q3. What is the Virtual DOM and how does React use it?
  6. Q4. What is the difference between functional and class components?
  7. Q5. What are props in React?
  8. Q6. What is state in React? How is it different from props?
  9. Q7. What is the React component lifecycle?
  10. Q8. What is reconciliation in React?
  11. Q9. What are keys and why do they matter?
  12. Q10. What is the difference between controlled and uncontrolled components?
  13. Section 2 - React Hooks (Q11–Q25)
  14. Q11. What are React Hooks and why were they introduced?
  15. Q12. How does `useState` work?
  16. Q13. How does `useEffect` work?
  17. Q14. What is the dependency array in `useEffect`?
  18. Q15. How do you clean up side effects in `useEffect`?
  19. Q16. What is `useRef` and when would you use it?
  20. Q17. What is `useMemo` and when should you use it?
  21. Q18. What is `useCallback` and how does it differ from `useMemo`?
  22. Q19. What is `useContext`? How does it compare to prop drilling?
  23. Q20. What is `useReducer`? When would you use it over `useState`?
  24. Q21. What is `useLayoutEffect`? How does it differ from `useEffect`?
  25. Q22. What are custom hooks? Write a `useFetch` example.
  26. Q23. What is `useId` in React 18?
  27. Q24. What is `useTransition`?
  28. Q25. What are the rules of hooks?
  29. Section 3 - Component Patterns & Architecture (Q26–Q35)
  30. Q26. What is prop drilling and how do you avoid it?
  31. Q27. What are Higher-Order Components (HOCs)?
  32. Q28. What is the render props pattern?
  33. Q29. What is component composition? How does it compare to inheritance?
  34. Q30. What is `React.memo`?
  35. Q31. What are Error Boundaries?
  36. Q32. What is `React.Fragment`?
  37. Q33. What is the Context API?
  38. Q34. What is lifting state up?
  39. Q35. What is the difference between presentational and container components?
  40. Section 4 - Performance Optimization (Q36–Q42)
  41. Q36. How does `React.lazy` and `Suspense` work?
  42. Q37. What is code splitting in React?
  43. Q38. How do you optimize list rendering in React?
  44. Q39. What causes unnecessary re-renders and how do you prevent them?
  45. Q40. What is the React Profiler?
  46. Q41. What is the role of the `key` prop in performance?
  47. Q42. What is windowing/virtualization and when should you use it?
  48. Section 5 - React Router (Q43–Q46)
  49. Q43. What is React Router v6? How does it differ from v5?
  50. Q44. How do you implement nested routes in React Router v6?
  51. Q45. How do you perform programmatic navigation in React Router v6?
  52. Q46. What is the difference between `<Link>` and `<NavLink>`?
  53. Section 6 - Modern React Patterns (Q47–Q50)
  54. Q47. What are React Server Components (RSC)?
  55. Q48. What is Suspense in React and how does it work with data fetching?
  56. Q49. What is Concurrent Mode in React 18?
  57. Q50. What is the difference between `startTransition` and `useTransition`?
  58. Frequently Asked Questions
  59. What React version should I focus on for 2026 interviews?
  60. How do I answer "when would you use Redux vs Context API"?
  61. Are class components still asked in interviews in 2026?
  62. What is the difference between `useEffect` and `useLayoutEffect`?
  63. How does React handle batching in React 18?
  64. Conclusion
Practice

Test your knowledge

Real interview questions asked at top product companies.

Practice Now
More Articles

React is the most-used frontend library for five consecutive years - 40.6% of all professional developers rely on it daily (Stack Overflow Developer Survey 2025). That staying power converts directly into interview demand: React developer roles consistently rank among the highest-paying frontend positions, with average total compensation exceeding $120,000 in the US (Built In, 2026).

The challenge isn't finding React interview questions - it's finding a single resource that covers concept, code, and interview nuance in one place. This guide answers all 50 questions you're likely to face at product companies and startups, organized by topic, each with a clean code snippet and an interview tip where it counts.

Key Takeaways

  • React is used by 40.6% of developers - the #1 frontend library for five straight years (Stack Overflow 2025)

  • Hooks (useState, useEffect, useCallback, useMemo) appear in virtually every React interview - know them cold

  • React 18 introduced Concurrent Mode, useTransition, and useId; React 19 stabilized Server Components

  • This guide covers all 6 interview buckets: fundamentals, hooks, patterns, performance, Router, and modern React

frontend interview prep roadmap → comprehensive guide to preparing for frontend developer interviews


Section 1 - React Fundamentals (Q1–Q10)

React was created by Jordan Walke at Meta and open-sourced in 2013. It's used by 40.6% of professional developers today, making it the most popular frontend library for five years running (Stack Overflow, 2025). Fundamentals questions test whether you understand React's core model - not just its API.

React JavaScript code on a dark screen with blue syntax highlighting
React JavaScript code on a dark screen with blue syntax highlighting

Q1. What is React? How is it different from a full framework?

React is a JavaScript library for building user interfaces - specifically, for building component trees that render to the DOM. It handles the view layer only. Unlike full frameworks such as Angular or Vue, React doesn't include a router, form library, or HTTP client out of the box. You compose those yourself from the ecosystem (React Router, React Hook Form, etc.).

// React's job: describe what the UI should look like
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// ReactDOM's job: put it on the actual page
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(<Greeting name="Abhijeet" />);

Interview tip: Interviewers often ask this to check if you understand why "React" and "React ecosystem" are different conversations.


Q2. What is JSX and how does the browser understand it?

JSX (JavaScript XML) is a syntax extension that lets you write HTML-like markup inside JavaScript. Browsers don't understand JSX - a build tool (Babel or the new React 17+ JSX transform) compiles it into React.createElement() calls before the browser ever sees it.

// What you write (JSX)
const element = <button className="btn">Click me</button>;

// What Babel compiles it to (React 17+ automatic runtime)
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("button", { className: "btn", children: "Click me" });

Since React 17, you no longer need import React from 'react' at the top of every file - the JSX transform handles it automatically.


Q3. What is the Virtual DOM and how does React use it?

The Virtual DOM is a lightweight JavaScript object representation of the real DOM tree. When state changes, React creates a new virtual DOM tree, diffs it against the previous one (reconciliation), and applies only the minimal set of changes to the real DOM.

State change → New Virtual DOM → Diff (reconciliation) → Real DOM patch
// React batches multiple setState calls and computes the minimum DOM ops
function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <p>{count}</p>
      {/* Only the <p> text node is updated in the real DOM - not the entire div */}
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

Interview tip: The Virtual DOM doesn't make React faster than direct DOM manipulation - it makes React fast enough while keeping code declarative and predictable.


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

Functional components are plain JavaScript functions that return JSX. They use hooks for state and side effects. Class components extend React.Component, use this.state, and manage lifecycle in methods like componentDidMount. Since React 16.8 (hooks), functional components can do everything class components can - and are the modern standard.

// Class component (legacy pattern)
class Welcome extends React.Component {
  state = { count: 0 };
  render() {
    return <h1>Count: {this.state.count}</h1>;
  }
}

// Functional component (modern standard)
function Welcome() {
  const [count, setCount] = React.useState(0);
  return <h1>Count: {count}</h1>;
}

Interview tip: If asked why hooks were introduced, the answer is: to share stateful logic between components without class boilerplate or HOC nesting.


Q5. What are props in React?

Props (properties) are read-only inputs passed from a parent component to a child. They're the primary mechanism for component communication in React. A child must never mutate its props - it should treat them as immutable.

// Parent passes props
function App() {
  return <UserCard name="Abhijeet" role="Frontend Dev" isActive={true} />;
}

// Child receives and uses props
function UserCard({ name, role, isActive }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{role}</p>
      <span>{isActive ? "Online" : "Offline"}</span>
    </div>
  );
}

Props can be any JavaScript value: strings, numbers, booleans, arrays, objects, or functions (callbacks).


Q6. What is state in React? How is it different from props?

State is mutable data managed inside a component. When state changes, React re-renders that component and its children. Props are passed in from outside and are read-only. The key rule: props come from the parent, state lives inside the component.

function Toggle() {
  const [isOn, setIsOn] = React.useState(false); // state - owned here

  return (
    <button onClick={() => setIsOn(prev => !prev)}>
      {isOn ? "ON" : "OFF"}
    </button>
  );
}

A common interview mistake is describing state as "private props" - state is actually data the component manages itself and can change over time. Props are external inputs that don't change unless the parent re-renders with new values.


Q7. What is the React component lifecycle?

Functional components with hooks map to lifecycle phases:

1. Mounting Phase

This is when a component is created and inserted into the DOM for the first time.

  • Logic: This is where you typically initialize state, set up subscriptions, or fetch initial data.

  • Hook Equivalent: useEffect(() => { ... }, [])

    • The empty dependency array [] ensures the code inside runs only once, immediately after the initial render.

  • Legacy Class Method: componentDidMount()

2. Updating Phase

This occurs whenever a component's props or state change, causing a re-render to keep the UI in sync with the data.

  • Logic: You use this phase to perform actions in response to specific data changes (e.g., re-fetching a user profile when a userId prop changes).

  • Hook Equivalent: useEffect(() => { ... }, [dependency])

    • By adding variables to the dependency array, the effect runs on mount and every time those specific variables change.

  • Legacy Class Method: componentDidUpdate()

3. Unmounting Phase

This is the final stage when a component is being removed from the DOM.

  • Logic: Crucial for "cleaning up" to prevent memory leaks. This includes clearing timers (setInterval), cancelling network requests, or removing event listeners.

  • Hook Equivalent: The Cleanup Function inside useEffect.

    JavaScript

    useEffect(() => {
      const timer = setInterval(() => console.log('Tick'), 1000);
    
      // Cleanup function
      return () => clearInterval(timer);
    }, []);
    
  • Legacy Class Method: componentWillUnmount()

function DataLoader({ id }) {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    // componentDidMount + componentDidUpdate (on id change)
    let cancelled = false;
    fetch(`/api/items/${id}`)
      .then(r => r.json())
      .then(d => { if (!cancelled) setData(d); });

    return () => { cancelled = true; }; // componentWillUnmount
  }, [id]);

  return <div>{data ? data.name : "Loading..."}</div>;
}

Q8. What is reconciliation in React?

Reconciliation is React's algorithm for computing the minimal DOM updates needed after a state change. React diffs the new Virtual DOM tree against the old one using two heuristics: (1) elements of different types produce different trees, and (2) the key prop tells React which list items are stable across renders.

// Without key - React re-renders all list items on any change
<ul>
  {items.map(item => <li>{item.name}</li>)} // ⚠️ Missing key
</ul>

// With key - React reuses existing DOM nodes efficiently
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>

Q9. What are keys and why do they matter?

Keys are stable, unique identifiers that tell React how to match items across renders during reconciliation. Without proper keys, React may reorder the wrong DOM nodes, causing subtle UI bugs (form input values in the wrong row, animations on the wrong element).

// Bad - index as key breaks when items are added/removed/reordered
{todos.map((todo, index) => <TodoItem key={index} todo={todo} />)}

// Good - stable unique id as key
{todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}

Interview tip: Always use a stable data ID as key. Index is acceptable only for static, non-reorderable lists.


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

A controlled component stores form input value in React state - React is the source of truth. An uncontrolled component stores value in the DOM itself, accessed via a ref. Controlled components are easier to validate and synchronize; uncontrolled components are simpler for file inputs or when integrating with non-React code.

// Controlled - React owns the value
function ControlledInput() {
  const [value, setValue] = React.useState("");
  return <input value={value} onChange={e => setValue(e.target.value)} />;
}

// Uncontrolled - DOM owns the value
function UncontrolledInput() {
  const inputRef = React.useRef(null);
  const handleSubmit = () => console.log(inputRef.current.value);
  return <input ref={inputRef} defaultValue="" />;
}

Section 2 - React Hooks (Q11–Q25)

React Hooks were introduced in React 16.8 (February 2019) and have since become the standard way to write React. According to the React team, hooks eliminated the need for class components while making stateful logic shareable across components without render props or HOC nesting.

Hooks and component architecture diagram on a whiteboard
Hooks and component architecture diagram on a whiteboard

Q11. What are React Hooks and why were they introduced?

Hooks are functions that let you "hook into" React state and lifecycle from functional components. They were introduced to solve three problems with class components: difficult reuse of stateful logic, complex components becoming hard to understand, and confusing this binding.

// Before hooks: HOC nesting to share stateful logic (confusing)
<AuthProvider>
  <ThemeProvider>
    <DataProvider>
      <MyComponent />
    </DataProvider>
  </ThemeProvider>
</AuthProvider>

// After hooks: compose logic directly in the component
function MyComponent() {
  const { user } = useAuth();
  const { theme } = useTheme();
  const { data } = useData();
  return <div style={theme}>{user.name}: {data.title}</div>;
}

Q12. How does `useState` work?

useState adds local state to a functional component. It returns a tuple: the current value and a setter function. React preserves state across re-renders and re-renders the component whenever the setter is called with a new value.

function Counter() {
  const [count, setCount] = React.useState(0);

  // Functional update - safe when new state depends on old state
  const increment = () => setCount(prev => prev + 1);

  // Direct update - fine when new state is independent
  const reset = () => setCount(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Interview tip: Always use the functional form setCount(prev => prev + 1) when the new state depends on the old value - especially in async callbacks.


Q13. How does `useEffect` work?

useEffect runs a side effect after the component renders. It replaces componentDidMount, componentDidUpdate, and componentWillUnmount from class components. The effect runs after every render by default, or conditionally based on its dependency array.

function WindowWidth() {
  const [width, setWidth] = React.useState(window.innerWidth);

  React.useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handler);

    // Cleanup: remove listener when component unmounts
    return () => window.removeEventListener("resize", handler);
  }, []); // Empty array = run once on mount

  return <p>Window width: {width}px</p>;
}

Q14. What is the dependency array in `useEffect`?

The dependency array controls when the effect re-runs. No array = runs after every render. Empty array [] = runs once on mount. Array with values = runs when any listed value changes between renders.

React.useEffect(() => {
  // Runs after every render
});

React.useEffect(() => {
  // Runs once on mount (like componentDidMount)
}, []);

React.useEffect(() => {
  // Runs on mount AND whenever userId changes
  fetchUser(userId);
}, [userId]);

Interview tip: ESLint's react-hooks/exhaustive-deps rule enforces correct dependencies. Don't suppress it - fix the underlying issue.


Q15. How do you clean up side effects in `useEffect`?

Return a cleanup function from useEffect. React calls it before the component unmounts and before re-running the effect on the next render cycle.

function Chat({ roomId }) {
  React.useEffect(() => {
    const socket = createSocket(roomId);
    socket.connect();

    return () => {
      socket.disconnect(); // cleanup runs before roomId changes or unmount
    };
  }, [roomId]);

  return <div>Chat room: {roomId}</div>;
}

Common cleanup targets: event listeners, timers (clearTimeout/clearInterval), WebSocket connections, and AbortControllers for cancelled fetch requests.


Q16. What is `useRef` and when would you use it?

useRef returns a mutable object { current: initialValue } that persists across renders without triggering re-renders when changed. Use it to: (1) access a DOM node directly, (2) store a mutable value that doesn't affect the UI, or (3) hold the previous value of a state variable.

function FocusInput() {
  const inputRef = React.useRef(null);

  const focusIt = () => inputRef.current.focus(); // direct DOM access

  return (
    <>
      <input ref={inputRef} placeholder="Type here..." />
      <button onClick={focusIt}>Focus</button>
    </>
  );
}

// Storing a mutable value (timer ID) without causing re-renders
function Timer() {
  const timerRef = React.useRef(null);

  const start = () => {
    timerRef.current = setInterval(() => console.log("tick"), 1000);
  };
  const stop = () => clearInterval(timerRef.current);

  return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}

Q17. What is `useMemo` and when should you use it?

useMemo memoizes the return value of a function. It recomputes only when its dependencies change. Use it for expensive computations that would otherwise run on every render.

function ProductList({ products, filterText }) {
  // Without useMemo: filters the entire array on every render
  // With useMemo: only recomputes when products or filterText changes
  const filtered = React.useMemo(
    () => products.filter(p => p.name.toLowerCase().includes(filterText)),
    [products, filterText]
  );

  return (
    <ul>
      {filtered.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

Interview tip: Don't over-use useMemo. It has overhead of its own (memory + comparison). Only apply it when you've profiled and identified an actual performance problem.


Q18. What is `useCallback` and how does it differ from `useMemo`?

useCallback memoizes a function reference so it doesn't get recreated on every render. useMemo memoizes the return value of a function. The key use case for useCallback is preventing child components wrapped in React.memo from re-rendering unnecessarily when a callback prop is passed.

function Parent() {
  const [count, setCount] = React.useState(0);

  // Without useCallback: new function reference every render → Child re-renders
  // With useCallback: same reference until deps change → Child stays memoized
  const handleClick = React.useCallback(() => {
    console.log("clicked");
  }, []); // no deps = stable reference forever

  return (
    <>
      <p>{count}</p>
      <button onClick={() => setCount(c => c + 1)}>Re-render parent</button>
      <MemoizedChild onClick={handleClick} />
    </>
  );
}

const MemoizedChild = React.memo(function Child({ onClick }) {
  console.log("Child rendered"); // only logs when onClick reference changes
  return <button onClick={onClick}>Click</button>;
});

Q19. What is `useContext`? How does it compare to prop drilling?

useContext reads the value of a React Context without needing to pass props through every intermediate component. It's the solution to prop drilling - when a prop is threaded through several layers of components just to reach a deeply nested one.

// Create context
const ThemeContext = React.createContext("light");

// Provide it high in the tree
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Layout />
    </ThemeContext.Provider>
  );
}

// Consume it anywhere in the tree - no prop drilling needed
function Button() {
  const theme = React.useContext(ThemeContext);
  return <button className={`btn-${theme}`}>Click</button>;
}

Q20. What is `useReducer`? When would you use it over `useState`?

useReducer manages state through a reducer function - a pure function that takes current state and an action, returns next state. Use it when: state logic is complex, the next state depends on multiple sub-values, or actions have names that make debugging easier.

const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT": return { ...state, count: state.count + state.step };
    case "DECREMENT": return { ...state, count: state.count - state.step };
    case "SET_STEP":  return { ...state, step: action.payload };
    default: throw new Error(`Unknown action: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count} (step: {state.step})</p>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
      <input
        type="number"
        value={state.step}
        onChange={e => dispatch({ type: "SET_STEP", payload: +e.target.value })}
      />
    </div>
  );
}

Q21. What is `useLayoutEffect`? How does it differ from `useEffect`?

useLayoutEffect fires synchronously after DOM mutations but before the browser paints. useEffect fires asynchronously after the paint. Use useLayoutEffect when you need to measure DOM layout (element sizes, positions) and apply changes before the user sees the initial render - otherwise use useEffect.

function Tooltip({ targetRef, text }) {
  const tooltipRef = React.useRef(null);

  // useLayoutEffect: measure and position before browser paints (no flicker)
  React.useLayoutEffect(() => {
    const rect = targetRef.current.getBoundingClientRect();
    tooltipRef.current.style.top = `${rect.bottom + 8}px`;
    tooltipRef.current.style.left = `${rect.left}px`;
  }, []);

  return <div ref={tooltipRef} className="tooltip">{text}</div>;
}

Q22. What are custom hooks? Write a `useFetch` example.

Custom hooks are functions starting with use that encapsulate and share stateful logic between components. They're the primary way to extract and reuse React logic without HOCs or render props.

// Custom hook: useFetch
function useFetch(url) {
  const [data, setData]     = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError]   = React.useState(null);

  React.useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { if (e.name !== "AbortError") { setError(e); setLoading(false); } });

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Usage - reusable across any component
function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);
  if (loading) return <p>Loading...</p>;
  if (error)   return <p>Error: {error.message}</p>;
  return <h2>{data.name}</h2>;
}

PERSONAL EXPERIENCE - Custom hooks are the most underutilized pattern by junior developers. Every time you write the same useEffect+useState pattern twice, it should become a custom hook.


Q23. What is `useId` in React 18?

useId generates a stable, unique ID that is consistent between server and client renders - solving the hydration mismatch problem that occurred when generating IDs with Math.random() or counters. Use it for accessibility attributes like htmlFor/id pairs.

function PasswordInput() {
  const id = React.useId(); // e.g. ":r0:" - stable across SSR and hydration

  return (
    <div>
      <label htmlFor={id}>Password</label>
      <input id={id} type="password" />
    </div>
  );
}

Q24. What is `useTransition`?

useTransition marks state updates as non-urgent, allowing React to keep the UI responsive by prioritizing urgent updates (like typing) over slow ones (like filtering a large list). It returns [isPending, startTransition].

function SearchPage() {
  const [query, setQuery]   = React.useState("");
  const [results, setResults] = React.useState([]);
  const [isPending, startTransition] = React.useTransition();

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value); // urgent: update the input immediately

    startTransition(() => {
      setResults(expensiveFilter(value)); // non-urgent: can be deferred
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <p>Updating results...</p>}
      <ResultsList items={results} />
    </>
  );
}

Q25. What are the rules of hooks?

There are two rules enforced by the eslint-plugin-react-hooks linter:

1. Only call hooks at the top level - Never inside loops, conditions, or nested functions. React relies on the call order being the same every render. 2. Only call hooks from React functions - Call them from functional components or other custom hooks, never from plain JS functions.

// ❌ Wrong - hook inside a condition (breaks call order)
function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = React.useState(null); // violates rule 1
  }
}

// ✅ Correct - condition inside the hook/component body
function Good({ isLoggedIn }) {
  const [user, setUser] = React.useState(null);
  if (!isLoggedIn) return null; // condition after hooks
  return <p>{user?.name}</p>;
}

Section 3 - Component Patterns & Architecture (Q26–Q35)

Understanding component design patterns separates candidates who can build features from those who can build scalable applications. These patterns appear frequently in system-design and architecture questions at senior-adjacent roles.

Component architecture diagram with nested boxes representing React component hierarchy
Component architecture diagram with nested boxes representing React component hierarchy

Q26. What is prop drilling and how do you avoid it?

Prop drilling happens when a prop is passed through multiple intermediate components that don't use it themselves - just to get it to a deeply nested child. It creates tight coupling and makes refactoring painful.

Solutions: React Context for read-mostly global data, component composition (passing components as children), or a state management library like Zustand or Redux Toolkit.

// Prop drilling (fragile - every level must pass userId down)
<App userId={userId}>
  <Layout userId={userId}>
    <Sidebar userId={userId}>
      <UserAvatar userId={userId} /> {/* only this component uses it */}
    </Sidebar>
  </Layout>
</App>

// Context (clean - UserAvatar reads it directly)
const UserContext = React.createContext(null);
<UserContext.Provider value={{ userId }}>
  <Layout>          {/* no prop needed */}
    <Sidebar>       {/* no prop needed */}
      <UserAvatar /> {/* reads from context */}
    </Sidebar>
  </Layout>
</UserContext.Provider>

Q27. What are Higher-Order Components (HOCs)?

A Higher-Order Component is a function that takes a component and returns a new component with added behaviour. It's a pattern for cross-cutting concerns like authentication guards, analytics tracking, or data fetching before hooks existed.

// HOC: adds auth guard to any component
function withAuth(WrappedComponent) {
  return function AuthGuard(props) {
    const { isAuthenticated } = useAuth();

    if (!isAuthenticated) return <Redirect to="/login" />;
    return <WrappedComponent {...props} />;
  };
}

// Usage
const ProtectedDashboard = withAuth(Dashboard);

Interview tip: HOCs are still valid (especially in libraries), but custom hooks have replaced them for most new code. Be ready to discuss both.


Q28. What is the render props pattern?

Render props is a pattern where a component accepts a function as a prop and calls it to render its children, sharing state or behaviour without hard-coding the UI.

// MouseTracker shares position via render prop
function MouseTracker({ render }) {
  const [pos, setPos] = React.useState({ x: 0, y: 0 });

  return (
    <div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
      {render(pos)} {/* caller decides what to render with the position */}
    </div>
  );
}

// Usage
<MouseTracker
  render={({ x, y }) => <p>Mouse at {x}, {y}</p>}
/>

Like HOCs, custom hooks have largely replaced render props for new code - but knowing the pattern is expected in interviews.


Q29. What is component composition? How does it compare to inheritance?

React strongly favors composition over inheritance. Composition means building complex UIs by combining smaller components - often via children or specialized props - rather than extending base classes. React's children prop is the simplest form of composition.

// Composition via children - Card is reusable with any content
function Card({ title, children }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-body">{children}</div>
    </div>
  );
}

function App() {
  return (
    <Card title="User Profile">
      <Avatar src="/user.png" />
      <p>Abhijeet Kushwaha - Frontend Dev</p>
    </Card>
  );
}

The React docs explicitly state: "We haven't found any use cases where we'd recommend creating component inheritance hierarchies."


Q30. What is `React.memo`?

React.memo is a higher-order component that memoizes a functional component's render output. React skips re-rendering a memoized component if its props haven't changed (shallow comparison). Pair it with useCallback on parent callbacks to get the full benefit.

// Without React.memo: re-renders every time Parent re-renders
function ExpensiveChild({ data }) {
  console.log("Child rendered");
  return <ul>{data.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
}

// With React.memo: skips render if `data` reference hasn't changed
const ExpensiveChild = React.memo(function({ data }) {
  console.log("Child rendered");
  return <ul>{data.map(d => <li key={d.id}>{d.name}</li>)}</ul>;
});

// Custom comparison (optional)
const OptimizedChild = React.memo(ExpensiveChild, (prevProps, nextProps) => {
  return prevProps.data.length === nextProps.data.length; // custom equality
});

Q31. What are Error Boundaries?

Error Boundaries are class components that catch JavaScript errors in their child tree during rendering, in lifecycle methods, and in constructors of the whole tree below them. They render a fallback UI instead of crashing the entire app. Functional components can't be error boundaries (yet) - you need a class component or a library like react-error-boundary.

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    // Log to error tracking service (Sentry, etc.)
    console.error("Boundary caught:", error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <h2>Something went wrong. <button onClick={() => this.setState({ hasError: false })}>Retry</button></h2>;
    }
    return this.props.children;
  }
}

// Usage
<ErrorBoundary>
  <FeatureThatMightCrash />
</ErrorBoundary>

Q32. What is `React.Fragment`?

React.Fragment lets you group multiple elements without adding an extra DOM node. It's useful when a component must return multiple elements but a wrapper

would break the layout (e.g., inside a or flexbox).

// With wrapper div - adds unnecessary DOM node, can break CSS
function Bad() {
  return (
    <div>
      <dt>Term</dt>
      <dd>Definition</dd>
    </div>
  );
}

// With Fragment - no extra DOM node
function Good() {
  return (
    <React.Fragment>
      <dt>Term</dt>
      <dd>Definition</dd>
    </React.Fragment>
  );
}

// Shorthand syntax (most common in practice)
function Better() {
  return (
    <>
      <dt>Term</dt>
      <dd>Definition</dd>
    </>
  );
}

Q33. What is the Context API?

The Context API provides a way to share data across the component tree without prop drilling. It has three parts: React.createContext() (creates the context), Context.Provider (supplies the value), and useContext() (consumes the value).

// auth-context.js
const AuthContext = React.createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = React.useState(null);

  const login  = (userData) => setUser(userData);
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const ctx = React.useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be used inside AuthProvider");
  return ctx;
}

// Any component in the tree
function NavBar() {
  const { user, logout } = useAuth();
  return <nav>{user ? <button onClick={logout}>Logout</button> : "Guest"}</nav>;
}

Q34. What is lifting state up?

Lifting state up means moving state to the nearest common ancestor of components that need to share it. The parent owns the state and passes it down as props, ensuring a single source of truth.

// Both inputs need to share the same temperature value
function TemperatureConverter() {
  const [celsius, setCelsius] = React.useState("");

  const toCelsius    = (f) => ((+f - 32) * 5) / 9;
  const toFahrenheit = (c) => (+c * 9) / 5 + 32;

  return (
    <div>
      <TemperatureInput
        scale="Celsius"
        value={celsius}
        onChange={setCelsius}
      />
      <TemperatureInput
        scale="Fahrenheit"
        value={celsius !== "" ? toFahrenheit(celsius).toFixed(1) : ""}
        onChange={f => setCelsius(toCelsius(f).toFixed(1))}
      />
    </div>
  );
}

Q35. What is the difference between presentational and container components?

Presentational components (also called "dumb" components) deal only with how things look. They receive all data via props and don't manage state or fetch data. Container components (or "smart" components) handle data fetching, state management, and pass data down to presentational components.

// Presentational - pure UI, no side effects
function UserCard({ name, avatar, role }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{role}</p>
    </div>
  );
}

// Container - manages data, renders the presentational component
function UserCardContainer({ userId }) {
  const { data, loading } = useFetch(`/api/users/${userId}`);
  if (loading) return <Skeleton />;
  return <UserCard name={data.name} avatar={data.avatar} role={data.role} />;
}

This pattern improves testability - you can test UserCard with static props, and test UserCardContainer for correct data fetching.


Section 4 - Performance Optimization (Q36–Q42)

React 18 ships with automatic batching, concurrent rendering, and Suspense for data - dramatically improving performance without any code changes. Still, knowing how to measure and fix bottlenecks is a core skill interviewers probe.

Q36. How does `React.lazy` and `Suspense` work?

React.lazy lets you dynamically import a component - deferring its bundle download until it's first needed. Suspense provides a fallback UI while the lazy component loads. Together they implement code splitting at the component level.

import React, { Suspense, lazy } from "react";

// Component is loaded only when this route is first visited
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings  = lazy(() => import("./pages/Settings"));

function App() {
  return (
    <Suspense fallback={<div>Loading page...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings"  element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Q37. What is code splitting in React?

Code splitting breaks your JS bundle into smaller chunks downloaded on demand. Without it, the entire app ships in one JS file - users wait for code they may never use. React supports code splitting via React.lazy + dynamic import(), and build tools (Webpack, Vite) handle the chunking.

// Dynamic import - Webpack / Vite automatically create a separate chunk
const HeavyChart = lazy(() => import("./components/HeavyChart"));

// Route-based splitting: most impactful strategy
// Each page route loads its own chunk only when visited
const routes = [
  { path: "/", component: lazy(() => import("./pages/Home")) },
  { path: "/analytics", component: lazy(() => import("./pages/Analytics")) },
];

Interview tip: Route-based splitting is the highest-ROI form. Component-level splitting (for modals, charts, heavy editors) is the next step.


Q38. How do you optimize list rendering in React?

Three techniques: (1) always provide stable key props, (2) wrap list items in React.memo to prevent unnecessary re-renders, (3) use virtualization for large lists to render only visible rows.

import { FixedSizeList as List } from "react-window";

// react-window only renders visible rows - handles 100,000 item lists smoothly
function VirtualList({ items }) {
  const Row = React.memo(({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  ));

  return (
    <List height={600} itemCount={items.length} itemSize={50} width="100%">
      {Row}
    </List>
  );
}

Q39. What causes unnecessary re-renders and how do you prevent them?

Common causes: parent re-renders (child re-renders by default), unstable function references passed as props, and Context value changing on every render.

// Problem: new object reference every render → MemoizedChild always re-renders
function Parent() {
  const config = { theme: "dark" }; // new object every render!
  return <MemoizedChild config={config} />;
}

// Fix: useMemo stabilizes the reference
function Parent() {
  const config = React.useMemo(() => ({ theme: "dark" }), []);
  return <MemoizedChild config={config} />;
}

// Context problem: new value object every render → all consumers re-render
<ThemeContext.Provider value={{ theme, setTheme }}> {/* new obj! */}

// Fix: memoize the context value
const value = React.useMemo(() => ({ theme, setTheme }), [theme]);
<ThemeContext.Provider value={value}>

Q40. What is the React Profiler?

The React DevTools Profiler records component render times and lets you identify which components render, how often, and why. It shows flame charts, ranked charts, and the reason for each render.

// Profiler API (programmatic, for CI or logging)
import { Profiler } from "react";

function onRenderCallback(id, phase, actualDuration, baseDuration) {
  // id: component tree name
  // phase: "mount" | "update"
  // actualDuration: time spent rendering this update
  // baseDuration: estimated time without memoization
  if (actualDuration > 16) { // flag renders > 1 frame (60fps)
    console.warn(`Slow render in ${id}: ${actualDuration.toFixed(2)}ms`);
  }
}

<Profiler id="Dashboard" onRender={onRenderCallback}>
  <Dashboard />
</Profiler>

Q41. What is the role of the `key` prop in performance?

The key prop is React's hint for reconciliation. A stable, unique key lets React reuse the existing DOM node when a list item's position changes. An unstable key (like Math.random() or array index in a reorderable list) forces React to unmount and remount the component - destroying local state and causing layout thrash.

// ❌ Math.random() key - remounts the component every render
{items.map(item => <Row key={Math.random()} item={item} />)}

// ❌ Index key in sorted/filtered list - wrong DOM reuse on reorder
{items.map((item, i) => <Row key={i} item={item} />)}

// ✅ Stable unique ID - React reuses the DOM node correctly
{items.map(item => <Row key={item.id} item={item} />)}

Q42. What is windowing/virtualization and when should you use it?

Virtualization (or windowing) renders only the rows visible in the viewport, not the entire list. For a 10,000-item list, it might render 20 DOM nodes regardless of list length. Use it when lists exceed ~200 items and you notice scroll jank or long initial renders. Libraries: react-window (lightweight) or react-virtual (TanStack).

import { useVirtualizer } from "@tanstack/react-virtual";

function VirtualTable({ rows }) {
  const parentRef = React.useRef(null);
  const virtualizer = useVirtualizer({
    count: rows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 45,
  });

  return (
    <div ref={parentRef} style={{ height: "500px", overflow: "auto" }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative" }}>
        {virtualizer.getVirtualItems().map(item => (
          <div key={item.key} style={{ position: "absolute", top: item.start, height: item.size, width: "100%" }}>
            {rows[item.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

Section 5 - React Router (Q43–Q46)

React Router v6 (released 2021, now at v6.x) was a significant rewrite that introduced , nested route layouts, and the useNavigate hook. It's the standard routing solution and appears in virtually every frontend interview.

Q43. What is React Router v6? How does it differ from v5?

React Router v6 replaced with (which always picks the best match), introduced relative paths, replaced useHistory with useNavigate, and added the component for nested layouts.

// React Router v5 (legacy)
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/users/:id" component={UserDetail} />
</Switch>

// React Router v6 (current)
import { Routes, Route } from "react-router-dom";

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/users/:id" element={<UserDetail />} />
  <Route path="*" element={<NotFound />} />
</Routes>

Q44. How do you implement nested routes in React Router v6?

Nest elements and use in the parent component where children should render.

// Route config
<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="analytics" element={<Analytics />} />
    <Route path="settings"  element={<Settings />} />
  </Route>
</Routes>

// DashboardLayout.jsx - renders shared UI + child route at <Outlet>
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />         {/* always visible */}
      <main>
        <Outlet />        {/* DashboardHome | Analytics | Settings renders here */}
      </main>
    </div>
  );
}

Q45. How do you perform programmatic navigation in React Router v6?

Use the useNavigate hook (replaces useHistory from v5).

import { useNavigate } from "react-router-dom";

function LoginForm() {
  const navigate = useNavigate();

  async function handleSubmit(e) {
    e.preventDefault();
    await login(credentials);
    navigate("/dashboard");                   // basic navigation
    navigate("/profile", { replace: true });  // replace history entry (no back button)
    navigate(-1);                             // go back one step in history
  }

  return <form onSubmit={handleSubmit}>...</form>;
}

Q46. What is the difference between `<Link>` and `<NavLink>`?

renders an anchor tag that navigates without a full page reload. does the same but also automatically applies an active class (and optional style) when its to path matches the current URL - making it ideal for navigation menus.

import { Link, NavLink } from "react-router-dom";

function Nav() {
  return (
    <nav>
      {/* Link - just navigates */}
      <Link to="/">Home</Link>

      {/* NavLink - adds active class when route matches */}
      <NavLink to="/dashboard" className={({ isActive }) => isActive ? "nav-active" : ""}>
        Dashboard
      </NavLink>

      {/* NavLink with inline style */}
      <NavLink to="/settings" style={({ isActive }) => ({ fontWeight: isActive ? "bold" : "normal" })}>
        Settings
      </NavLink>
    </nav>
  );
}

Section 6 - Modern React Patterns (Q47–Q50)

React 19 (December 2024) stabilized Server Components and introduced the use hook, Actions, and new form handling APIs. These questions appear in interviews at companies already on Next.js 13+ or Remix.

Q47. What are React Server Components (RSC)?

React Server Components render on the server at request time (or build time) and send HTML + a serialized component description to the client. They have zero JS bundle impact, can directly access databases and secrets, but can't use state, effects, or browser APIs. Client Components still handle interactivity.

// app/page.jsx - Server Component (no "use client" directive)
// Runs on the server - can fetch data directly, no useEffect needed
async function ProductPage({ params }) {
  const product = await db.products.findById(params.id); // direct DB access
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* Client Component for interactivity */}
      <AddToCartButton productId={product.id} />
    </div>
  );
}

// components/AddToCartButton.jsx - Client Component
"use client";
function AddToCartButton({ productId }) {
  const [added, setAdded] = React.useState(false); // state OK here
  return <button onClick={() => setAdded(true)}>{added ? "Added!" : "Add to Cart"}</button>;
}

Server Components don't replace client components - they complement them. The mental model is: push data fetching and static rendering to the server, keep interactivity on the client. This split is what Next.js App Router is built around.


Q48. What is Suspense in React and how does it work with data fetching?

catches "pending" signals from its children and shows a fallback until the data (or lazy component) is ready. React 18 extended Suspense to work with data fetching when using Suspense-compatible data sources (React Query, Relay, Next.js App Router, or the use() hook in React 19).

// React 19 - use() hook unwraps a promise and integrates with Suspense
import { use, Suspense } from "react";

function UserProfile({ userPromise }) {
  const user = use(userPromise); // suspends until resolved
  return <h2>{user.name}</h2>;
}

function App() {
  const userPromise = fetch("/api/user").then(r => r.json());

  return (
    <Suspense fallback={<Skeleton />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

Q49. What is Concurrent Mode in React 18?

Concurrent Mode is React 18's rendering model that allows React to interrupt, pause, and resume rendering work. Instead of blocking the main thread for a long render, React can pause mid-render to handle a higher-priority update (like a user keystroke), then resume. It's enabled by default when you use createRoot.

// React 17 - legacy render (synchronous, blocking)
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));

// React 18 - concurrent root (non-blocking, opt-in to all concurrent features)
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(<App />);

Concurrent features built on top of this: useTransition, useDeferredValue, Suspense for data, automatic batching, and Selective Hydration.


Q50. What is the difference between `startTransition` and `useTransition`?

Both mark updates as non-urgent (concurrent transitions), but they serve different purposes:

- startTransition - a static function import. Use when you don't need the isPending flag. - useTransition - a hook. Returns [isPending, startTransition]. Use when you need to show a loading indicator during the transition.

import { startTransition } from "react";

// startTransition: simpler, no pending state
function SearchBox({ onSearch }) {
  return (
    <input onChange={e => {
      startTransition(() => onSearch(e.target.value));
    }} />
  );
}

// useTransition: with pending state for loading indicator
function SearchBoxWithIndicator({ onSearch }) {
  const [isPending, startTransition] = React.useTransition();

  return (
    <>
      <input onChange={e => startTransition(() => onSearch(e.target.value))} />
      {isPending && <span className="spinner" />}
    </>
  );
}

Frequently Asked Questions

What React version should I focus on for 2026 interviews?

React 18 is the primary focus - it introduced Concurrent Mode, automatic batching, useTransition, useId, and the startTransition API, all of which appear in interviews. React 19 (stable, December 2024) introduced Server Components and the use() hook; expect questions on RSC if you're interviewing at Next.js or Remix shops.

React 18 vs React 19 differences → detailed comparison of React 18 and 19 features

How do I answer "when would you use Redux vs Context API"?

Context API is fine for low-frequency updates (auth, theme, locale). Redux (or Zustand) is better when: multiple components update state frequently, you need DevTools time-travel debugging, or state mutations require strict action-based tracing. According to the 2025 State of JS survey, Zustand has overtaken Redux in developer satisfaction for new projects.

Are class components still asked in interviews in 2026?

Yes, but only in context. Interviewers at companies with legacy codebases ask about lifecycle methods and this binding. The consensus in new projects is functional components + hooks. Know class components conceptually (especially Error Boundaries), but lead answers with functional equivalents.

What is the difference between `useEffect` and `useLayoutEffect`?

useEffect runs after the browser paints - fire-and-forget for data fetching, subscriptions, and logging. useLayoutEffect runs before the browser paints - use it for DOM measurements and position corrections that must be applied before the user sees the first frame. Defaulting to useEffect is almost always correct.

How does React handle batching in React 18?

React 18 introduced automatic batching: multiple state updates in any async context (setTimeout, Promises, native event handlers) are batched into a single re-render. In React 17, only updates inside React event handlers were batched. To opt out, use ReactDOM.flushSync().

React 18 automatic batching deep dive → detailed guide on React 18 batching behavior


Conclusion

React interview questions in 2026 span six layers: fundamentals, hooks, architecture patterns, performance, routing, and modern concurrent features. Hooks dominate - useState, useEffect, useCallback, useMemo, and useContext appear in virtually every interview, with useTransition and custom hooks increasingly common at senior levels.

The questions that separate strong candidates aren't recall questions - they're the ones that combine concepts: "why does React.memo not help if you pass a new callback prop?" or "how would you share state between sibling components without prop drilling?". Work through the code examples in this guide until you can write them from memory, and you'll be ready.

Key Takeaways

  • React fundamentals (Virtual DOM, reconciliation, controlled components) are table stakes - know them cold

  • Hooks are the heart of modern React - master the rules, the dependency array, and custom hooks

  • Performance: React.memo + useCallback + useMemo only help together - profile before you optimize

  • React 18's useTransition and RSC in React 19 are the differentiating questions at senior-adjacent levels

JavaScript interview questions 2026 → complete guide to JavaScript fundamentals asked in interviews

Author: Abhijeet Kushwaha | Last updated: April 2026

More from React & Frontend

⚛️

30 Golden Frontend Topics Every Senior Developer Must Master in 2026

18 min read

⚛️

The Modern React Stack in 2026: Zustand, TanStack Query, Tailwind, and shadcn/ui Explained

18 min read

⚛️

React Server Components Security in 2026: CVEs, the React Foundation, and What It All Means for You

10 min read

Browse All Articles