React Interview Questions: 25 Real-World Questions for Experienced Developers

Skip the memorized React definitions. These 25 practical React interview questions focus on state, Effects, rendering bugs, performance, Server Components, Suspense and decisions developers face in real applications.

react-js-interview- questions
guide.md READY

I don’t think memorizing definitions is a good way to prepare for a React interview.

Knowing that useState stores state or that useEffect handles Effects is useful, but experienced-developer interviews usually become more interesting when the interviewer changes the question slightly:

This component works.

Why does it work?

What could break?

How would you redesign it?

What happens when the data changes quickly?

Would this still be your approach in a larger application?

Those are the questions I find more useful because they test whether someone understands React’s model rather than whether they remember an API definition.

So instead of another list of 50 one-line answers, I’ve rewritten this guide around React interview questions that require reasoning.

The examples cover modern React, including state snapshots, Effects, Suspense, transitions, Server Components and React Compiler, while still focusing heavily on fundamentals that matter regardless of which framework you use.

The current React documentation is on React 19.2. If you’re preparing for an interview, I recommend using the official React documentation alongside this guide instead of relying only on interview-question sites.

1. Why Doesn’t This Increment the Counter Three Times?

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

  function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      {count}
    </button>
  );
}

This is a good question because it tests whether someone understands that React state behaves like a snapshot for a particular render.

All three calls use the same count value from the current render.

If the next state depends on the previous state, use an updater function:

function handleClick() {
  setCount(current => current + 1);
  setCount(current => current + 1);
  setCount(current => current + 1);
}

Now each update receives the result of the previous queued update.

A strong interview answer should mention state snapshots and queued updates rather than simply saying “React batches state.”

2. Why Can Using the Array Index as a Key Cause a UI Bug?

Imagine editable rows:

{users.map((user, index) => (
  <UserRow
    key={index}
    user={user}
  />
))}

If the list is reordered, inserted into or filtered, the index may now refer to a different item.

React uses keys to understand component identity.

So a local state value associated with one row can appear attached to another row when unstable keys are used.

If the data has a stable ID, I prefer:

{users.map(user => (
  <UserRow
    key={user.id}
    user={user}
  />
))}

The important interview answer is not “indexes are always bad.”

An index may be acceptable for a static list whose order and contents never change. The problem appears when the key stops representing stable identity.

3. What’s Wrong with This useEffect?

const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');

useEffect(() => {
  setFullName(
    `${firstName} ${lastName}`
  );
}, [firstName, lastName]);

The problem is not that the Effect is syntactically invalid.

The problem is that fullName is derived entirely from existing state.

I would simply calculate it during rendering:

const fullName =
  `${firstName} ${lastName}`;

Now there is one source of truth instead of three pieces of state that must stay synchronized.

This matches React’s current guidance: if you’re not synchronizing with an external system, you probably don’t need an Effect.

4. Why Does My Effect Run Twice During Development?

This often appears when Strict Mode is enabled.

useEffect(() => {
  const connection =
    createConnection();

  connection.connect();

  return () => {
    connection.disconnect();
  };
}, []);

In development, React can run an extra setup → cleanup → setup cycle.

This is intentional. It helps reveal Effects that do not clean up properly.

I wouldn’t “fix” this by adding a flag that prevents the Effect from running twice.

I would make sure setup and cleanup are symmetrical.

React’s current useEffect documentation specifically describes this development-only behaviour.

5. What’s the Difference Between State and a Ref?

I think about the difference primarily in terms of rendering.

State stores information that affects rendered output.

const [isOpen, setIsOpen] =
  useState(false);

A ref stores mutable information that React does not need in order to render the UI.

const timerRef = useRef(null);

Updating:

timerRef.current = timerId;

does not trigger a render.

Typical ref use cases include DOM references, timers and values that need to survive renders without affecting the visual output.

6. What Is a Stale Closure in React?

Consider:

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

  function handleAlert() {
    setTimeout(() => {
      alert(count);
    }, 3000);
  }

  // ...
}

The callback captures the count value from the render in which handleAlert was created.

If the count changes before the timer runs, the callback does not magically start reading the newest render’s variable.

This behaviour comes from JavaScript closures, but React’s render model makes it particularly important to understand.

Depending on the requirement, the solution may involve an updater function, a ref, changing the architecture, or using newer APIs such as Effect Events for specific Effect-related cases.

I wouldn’t answer every stale-closure problem with useRef. The correct solution depends on whether the code should use the historical value or the latest value.

7. Controlled or Uncontrolled Form Input: Which Is Better?

A controlled input gets its value from React:

const [email, setEmail] =
  useState('');

<input
  value={email}
  onChange={event =>
    setEmail(event.target.value)
  }
/>

An uncontrolled input leaves the current value primarily in the DOM and may be read through a ref or form submission.

I don’t think one is universally better.

Controlled inputs are useful when React needs immediate knowledge of the value for validation, conditional UI or other behaviour.

Uncontrolled inputs can be simpler when React doesn’t need to react to every keystroke.

A good answer explains the trade-off instead of saying all React forms must be controlled.

8. When Would You Use useReducer Instead of useState?

I don’t choose useReducer simply because a component has several state variables.

I consider it when related state transitions become easier to express as actions.

function cartReducer(state, action) {
  switch (action.type) {
    case 'itemAdded':
      return addItem(
        state,
        action.product
      );

    case 'itemRemoved':
      return removeItem(
        state,
        action.productId
      );

    case 'cleared':
      return [];

    default:
      return state;
  }
}

The advantage is not automatically better performance.

The advantage is that complicated transitions can become explicit and easier to test and reason about.

9. Where Should State Live?

Suppose two sibling components both need the same selected customer:

        CustomerPage
        /          \
       v            v
CustomerList    CustomerDetails

I normally place the shared state in their nearest common owner:

CustomerPage
   |
   +-- selectedCustomerId
   |
   +----> CustomerList
   |
   +----> CustomerDetails

I wouldn’t automatically reach for Context or Redux.

Keeping state as local as possible usually makes the data flow easier to understand.

10. When Is Context a Bad Choice?

Context solves a real problem: making a value available deeply in a tree without manually passing it through every intermediate component.

But turning Context into one giant application store can create broad dependencies and unnecessary updates.

I would be comfortable putting values such as these in appropriate contexts:

  • theme
  • locale
  • authenticated-user information
  • stable application-level configuration

I become more careful when one Context contains a large rapidly changing object used by dozens of unrelated components.

Sometimes splitting contexts, moving state closer to consumers, or using a purpose-built state solution creates a cleaner architecture.

11. Should You Wrap Every Function in useCallback?

No.

This was already questionable advice before React Compiler, and it is even less useful as a blanket rule now.

useCallback caches a function reference:

const handleSave = useCallback(() => {
  saveProduct(productId);
}, [productId]);

That can be useful when function identity matters—for example when working with a memoized child or an Effect dependency.

But adding it everywhere creates dependencies and additional code without necessarily making anything faster.

Modern React also has React Compiler, which can automatically memoize values and functions. React still provides useCallback when explicit control is useful.

So my interview answer would be:

Measure or identify the identity problem first. Don’t memoize everything by habit.

12. useMemo vs useCallback: What’s the Real Difference?

useMemo caches the result of a calculation:

const visibleProducts = useMemo(
  () => filterProducts(
    products,
    search
  ),
  [products, search]
);

useCallback caches the function itself:

const handleSelect =
  useCallback(
    productId => {
      setSelected(productId);
    },
    []
  );

But the stronger interview answer should go one step further.

Neither should be treated as correctness APIs. They are primarily optimization/control tools.

React Compiler 1.0 is now stable and performs automatic memoization, reducing the amount of manual memoization new code may require. The React team still keeps these hooks available when developers need precise control. See the React Compiler documentation.

13. A Page Feels Slow. What Would You Optimize First?

I would not start by adding useMemo to random functions.

First I want to identify where the time is going.

I would investigate things such as:

  • large JavaScript bundles
  • expensive renders
  • large lists
  • unnecessary network waterfalls
  • slow APIs
  • expensive calculations
  • frequently changing global state

React DevTools Profiler can help identify which components are actually expensive.

I prefer optimization driven by evidence rather than optimization driven by API availability.

14. How Would You Render 100,000 Rows?

I wouldn’t render 100,000 DOM rows at once if the user can only see a few dozen.

I would look at virtualization/windowing.

100,000 records
       |
       v
Visible window
       |
       v
Maybe 30–60 rendered rows

Other decisions may include server-side pagination, filtering, infinite loading and keeping expensive row components small.

The important idea is reducing the amount of work rather than attempting to memoize an enormous DOM tree into becoming cheap.

15. What Is useTransition Solving?

Some updates are urgent.

If a user types into an input, the input should respond immediately.

Other updates can happen with lower priority.

Imagine that typing also changes an expensive results view:

const [isPending, startTransition] =
  useTransition();

function handleTabChange(nextTab) {
  startTransition(() => {
    setTab(nextTab);
  });
}

A Transition lets React treat the update as non-urgent so urgent interactions can remain responsive.

I wouldn’t describe a Transition as simply “making React asynchronous.”

It communicates update priority and lets React interrupt non-urgent rendering work where appropriate.

16. useDeferredValue vs Debouncing: Are They the Same?

No.

Debouncing intentionally delays when an operation starts.

User types
   |
wait 300ms
   |
API request

useDeferredValue lets part of the UI lag behind a more urgent value.

const deferredSearch =
  useDeferredValue(search);

The input can continue showing the latest value while a slower result view works from a deferred value.

It does not automatically reduce the number of network requests the way a debounce strategy can.

17. What Does Suspense Actually Do?

Suspense provides a boundary where React can display fallback UI while part of the tree is waiting.

<Suspense fallback={
  <ProductSkeleton />
}>
  <ProductDetails />
</Suspense>

An interview answer should avoid describing Suspense as simply “a loading spinner API.”

The boundary participates in React’s rendering model. It can work with lazy-loaded components and framework-supported asynchronous resources, and is an important part of streaming and Server Component architectures.

React 19.2 also continues expanding Suspense-related rendering capabilities rather than using the old “Concurrent Mode” terminology found in many interview lists. :contentReference[oaicite:2]{index=2}

18. Why Can’t try/catch Catch a Child Component’s Render Error?

function Parent() {
  try {
    return <Child />;
  } catch (error) {
    return <Fallback />;
  }
}

The try block is creating the React element. It isn’t executing the child’s rendering in the same synchronous call stack in the way this code assumes.

Rendering failures in a descendant should be handled with an Error Boundary.

<ErrorBoundary
  fallback={<Fallback />}
>
  <Child />
</ErrorBoundary>

Error Boundaries can prevent one failing subtree from taking down the entire visible application.

React’s current lint guidance explicitly notes that normal try/catch cannot catch errors thrown during rendering in child components. :contentReference[oaicite:3]{index=3}

19. What Is the Difference Between SSR and Server Components?

This is a very good modern React interview question because the two concepts are regularly confused.

SSR is about producing initial HTML on the server.

React tree
    |
    v
Server creates HTML
    |
    v
Browser receives HTML
    |
    v
Client hydration

Server Components are components that execute in a server environment and whose implementation is not sent to the browser as client component JavaScript.

They can access server-side resources directly:

async function ProductPage({
  id
}) {
  const product =
    await db.products.get(id);

  return (
    <ProductDetails
      product={product}
    />
  );
}

A framework can combine Server Components with SSR, but they solve different problems.

React’s official Server Components documentation makes this separation explicit. :contentReference[oaicite:4]{index=4}

20. Can a Server Component Use useState?

No.

A Server Component isn’t an interactive component executing in the browser.

Interactive behaviour belongs in Client Components.

// Server Component

async function ProductPage() {
  const product =
    await loadProduct();

  return (
    <>
      <ProductDetails
        product={product}
      />

      <AddToCart
        productId={product.id}
      />
    </>
  );
}

The interactive component can establish a client boundary:

'use client';

import { useState } from 'react';

export function AddToCart() {
  const [quantity, setQuantity] =
    useState(1);

  // ...
}

A useful interview detail: there is no 'use server' directive for declaring a Server Component.

'use server' marks Server Functions. React explicitly calls out this distinction in its documentation. :contentReference[oaicite:5]{index=5}

21. What Is a Server Function?

Server Functions allow client code to invoke an asynchronous function that executes on the server when used through an RSC-compatible framework.

export async function saveProfile(
  formData
) {
  'use server';

  const name =
    formData.get('name');

  // Validate.
  // Authorize.
  // Save on server.
}

The term matters because older material often calls every Server Function a “Server Action.”

Current React documentation uses Server Function for the general concept. A Server Function used through an action is a Server Action. :contentReference[oaicite:6]{index=6}

Another important interview point: a Server Function should be treated as an exposed server endpoint.

Authentication and authorization still have to happen on the server. A function being called from your React UI does not make the request trusted.

22. How Would You Prevent an API Race Condition?

Imagine a search page.

User searches "re"
        |
        +--- Request A

User quickly searches "react"
        |
        +--- Request B

Request B returns first

Request A returns later

Old results replace new results

This is not really a React-specific networking problem, but React components frequently expose it.

If I’m manually fetching inside an Effect, I need a cleanup or cancellation strategy.

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

  async function search() {
    const response = await fetch(
      `/api/search?q=${query}`,
      {
        signal:
          controller.signal
      }
    );

    const data =
      await response.json();

    setResults(data);
  }

  search();

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

In a larger application, framework loaders or data-fetching libraries may manage cancellation, caching and race conditions more cleanly than manually duplicating this logic throughout components.

23. Where Should Authentication Be Enforced?

This is intentionally a trickier question.

Showing or hiding UI in React is not sufficient authorization.

{user.isAdmin && (
  <DeleteUserButton />
)}

That improves the user interface.

It does not protect:

DELETE /api/users/123

The server must authenticate the request and enforce authorization before performing the operation.

I think of it as:

React UI check
    |
    +-- user experience


Server authorization
    |
    +-- actual security boundary

This distinction becomes particularly important with Server Functions too. A server-executed function that can be invoked by a client still needs proper authorization.

24. What Causes a Hydration Mismatch?

Hydration expects the initial client render to agree with the HTML that the server produced.

Something like this can create trouble:

function Clock() {
  return (
    <p>
      {new Date().toISOString()}
    </p>
  );
}

The server renders at one moment.

The browser renders slightly later.

Other common sources include:

  • random values during rendering
  • browser-only data affecting initial output
  • different locale/timezone output
  • invalid HTML nesting
  • different server and browser data

A strong answer focuses on deterministic initial rendering instead of treating hydration warnings as something to suppress.

25. What Would You Test in This Component?

function CheckoutButton({
  cart,
  onCheckout
}) {
  const disabled =
    cart.length === 0;

  return (
    <button
      disabled={disabled}
      onClick={onCheckout}
    >
      Checkout
    </button>
  );
}

I prefer tests around observable behaviour rather than implementation details.

Useful behaviours include:

  • The button is disabled when the cart is empty.
  • The button is enabled when an item exists.
  • Activating the enabled button calls the checkout behaviour.

I would not make the test depend on whether the component happens to use useMemo, a particular helper function or another internal implementation detail.

If I can refactor the internal code without changing behaviour and the test still passes, that is generally a healthier test.

Bonus: What Changed About React Performance Questions?

Older React interviews often expected an answer like:

React.memo
+ useMemo
+ useCallback
= optimized React

I wouldn’t consider that a strong answer anymore.

React Compiler 1.0 is now stable and automatically performs memoization based on its analysis of a component. The React team recommends relying on the compiler for memoization in new code where it is being used, while keeping manual memoization APIs available for cases requiring explicit control. :contentReference[oaicite:7]{index=7}

That doesn’t mean performance knowledge has become unnecessary.

I would argue the opposite.

A developer should still understand:

  • what causes rendering
  • state ownership
  • component identity
  • large DOM trees
  • network waterfalls
  • code splitting
  • server/client boundaries
  • profiling

Automatically memoizing a calculation cannot fix an application that downloads too much JavaScript, renders 100,000 DOM elements or makes six sequential API requests that could have happened in parallel.

Bonus: What’s New Enough in React 19.2 to Know for an Interview?

I wouldn’t reject someone because they can’t recite every React 19.2 API.

But an experienced React developer should at least be aware that the ecosystem has moved beyond the React 16/17 interview-question era.

React 19.2 includes features such as:

  • <Activity />
  • useEffectEvent
  • additional server-rendering improvements
  • React performance tracks

The official React 19.2 announcement is worth reading if you’re interviewing for a role that expects current React knowledge.

I would still spend more preparation time understanding state, rendering, Effects and architecture than memorizing a release-note list.

How I Would Answer React Interview Questions

For experienced roles, I try not to give an API definition and stop.

I would answer in roughly this shape:

1. Explain the concept.

2. Give a small example.

3. Explain when I would use it.

4. Explain when I would not use it.

5. Mention the trade-off or failure case.

For example, if someone asks about Context, I wouldn’t simply say:

“Context avoids prop drilling.”

I would explain that Context makes a value available deeper in a tree, give an example such as theme or authentication state, and then mention that a large frequently changing Context can create broad dependencies and that state should still stay local when possible.

That answer demonstrates much more than memorizing the first sentence from documentation.

What I Would Focus on for a Senior React Interview

If I had limited preparation time, I would focus heavily on these areas:

  • React’s rendering model and state snapshots
  • component identity and keys
  • state ownership
  • Effects and cleanup
  • closures and dependency problems
  • forms
  • performance profiling
  • large datasets and virtualization
  • Suspense and transitions
  • SSR and hydration
  • Server Components and Client Components
  • security boundaries
  • testing behaviour rather than implementation
  • the impact of React Compiler

I would spend less time memorizing definitions of things such as Fiber internals unless the particular role specifically needs React internals.

Final Thoughts

The React questions I find most useful are rarely the ones with a one-sentence answer.

“What is useState?” tells me much less than:

“Why did this state update produce a value you weren’t expecting?”

“What is useEffect?” tells me much less than:

“Does this code need an Effect at all?”

And “What is useMemo?” tells me much less than:

“Where is the actual performance bottleneck, and how do you know?”

That is how I would prepare for an experienced React interview.

Don’t only learn what an API does. Understand the problem it solves, what React is doing underneath the behaviour you can see, and when a different approach would produce simpler code.

If you can explain the trade-off instead of only naming the Hook, your interview answer becomes much stronger.

Continue Learning

If you’re preparing for React development or interviews, these guides continue from here:

Share this guideLinkedInPost

ARTICLE TOOLKIT

Save or share this guide

Keep the reference nearby or send it to a teammate solving the same problem.

Share this guideLinkedInPost

QUALITY NOTE

Written from practical development experience and reviewed for clarity. Found an outdated step?

Report a correction →

Jaydip Barad

WRITTEN BY

Jaydip Barad

Senior full-stack developer sharing production-tested lessons from 14+ years of building backend systems, WordPress platforms and modern JavaScript applications.

Node.jsTypeScriptWordPressArchitecture
Previous guide
Next guide

THE PRACTICAL DEVELOPER LETTER

Get useful engineering lessons without the noise.

New tutorials, architecture notes and tools worth knowing—delivered occasionally.




    Occasional practical tutorials. Unsubscribe any time.