interviewDeck

Your one-stop platform to prepare, practice and ace your interviews.

Loading your questions…

All Questions

Filters & tools

React Interview Questions and Answers

34 hand-picked React interview questions with detailed answers. Open the interactive version above to search, filter by difficulty, run code, bookmark questions and track your progress.

What is React and how is it different from a full framework like Angular?

React is a JavaScript library for building UIs with a component model and declarative rendering. Facebook (Meta) open-sourced it; it focuses on the view layer.

Unlike Angular (a full framework with routing, DI, forms, HTTP, and a CLI opinionated structure), React is intentionally minimal. Routing, global state, and data fetching come from the ecosystem (React Router, Redux/Zustand, TanStack Query, Next.js).

  • React — library + ecosystem choices
  • Angular — batteries-included framework

What is JSX and how does it work under the hood?

JSX is a syntax extension that looks like HTML inside JavaScript. Build tools (Babel, SWC) transpile it to React.createElement (or the automatic JSX runtime) calls that produce React elements — plain objects describing the UI.

JSX is not HTML: attributes use camelCase (className, onClick), expressions go in { }, and you return one parent (or a Fragment).

// JSX
const el = <h1 className="title">Hello {name}</h1>;

// Roughly compiles to
React.createElement('h1', { className: 'title' }, 'Hello ', name);

Explain the Virtual DOM and reconciliation.

The Virtual DOM is a lightweight in-memory tree of React elements. When state changes, React builds a new virtual tree, diffs it against the previous one (reconciliation), and applies the minimal set of real DOM updates.

This lets you write declarative UI ("UI = f(state)") while React batches and optimizes imperative DOM work. Keys help React match list items across updates so it reuses DOM nodes instead of remounting them.

Functional components vs class components — which should you use today?

Functional components are plain functions that take props and return JSX. With Hooks they handle state, effects, and context — this is the modern default.

Class components use extends React.Component, lifecycle methods (componentDidMount, etc.), and this.state. Still work, but new code and docs focus on functions + Hooks.

Prefer functional components unless maintaining a legacy class codebase.

function Greeting({ name }) {
  return <h1>Hello, {name}</h1>;
}

What is the difference between props and state?

Props are inputs passed from a parent — read-only for the child. Changing props is the parent's job.

State is data owned by the component. Updating state with a setter (e.g. setCount) schedules a re-render.

  • Props — configuration from outside
  • State — internal, mutable over time via setters

Lift state up when siblings need to share it; pass data down as props.

function Counter() {
  const [count, setCount] = useState(0); // state
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

function App() {
  return <Counter />; // no props here; parent could pass initialCount as a prop
}

How does useState work?

useState(initial) returns [value, setValue]. Calling the setter queues an update and re-renders the component with the new value.

Use the functional updater setX(prev => ...) when the next value depends on the previous one (especially in async callbacks or batched updates). Initial state can be a lazy function useState(() => expensive()) so it runs only once.

const [user, setUser] = useState(null);
setUser(prev => ({ ...prev, name: 'Ada' }));

Explain useEffect — when does it run and what is the cleanup function for?

useEffect(fn, deps) runs after paint to synchronize with external systems (network, DOM APIs, subscriptions, timers).

  • No deps array — after every render
  • [] — once after mount (and cleanup on unmount)
  • [a, b] — when a or b change

Return a cleanup function to unsubscribe, clear timers, or abort fetches — React runs it before the next effect and on unmount.

useEffect(() => {
  const id = setInterval(() => tick(), 1000);
  return () => clearInterval(id);
}, []);

What are the Rules of Hooks?

Two rules:

  1. Only call Hooks at the top level — not inside loops, conditions, or nested functions. Order must be stable across renders.
  2. Only call Hooks from React functions — function components or custom Hooks (names starting with use).

Violating them breaks React's internal Hook linked list (state from the wrong Hook).

// BAD
if (cond) {
  const [x, setX] = useState(0);
}

// GOOD — always call, branch inside
const [x, setX] = useState(0);
if (cond) { /* use x */ }

When do you use useRef?

useRef(initial) returns a mutable object { current: ... } that persists across renders and updating it does not trigger a re-render.

Common uses:

  • Hold a DOM node: ref={inputRef} then inputRef.current.focus()
  • Store previous values, timer IDs, or latest callback without re-subscribing effects
const inputRef = useRef(null);

function focus() {
  inputRef.current?.focus();
}

return <input ref={inputRef} />;

useMemo vs useCallback — when are they worth using?

useMemo(() => value, deps) caches a computed value between renders.

useCallback(fn, deps) caches a function reference (sugar for useMemo(() => fn, deps)).

Use them when:

  • Passing callbacks/objects to memoized children (React.memo) to avoid pointless re-renders
  • Expensive calculations
  • Stable deps for effects that shouldn't re-run every render

Don't wrap everything — premature memoization adds complexity and can be slower for cheap work.

const sorted = useMemo(() => [...items].sort(cmp), [items]);
const onSelect = useCallback(id => setSelected(id), []);

How does Context API work and when should you use it?

createContext + Provider + useContext let you pass data through the tree without prop drilling.

Good for: theme, auth user, locale, feature flags — relatively stable, widely needed values.

Weak for: high-frequency updates (every keystroke) across a large tree — all consumers re-render. Split contexts, memoize provider value, or use a dedicated state library for complex client state.

const ThemeContext = createContext('light');

function App() {
  const [theme, setTheme] = useState('dark');
  const value = useMemo(() => ({ theme, setTheme }), [theme]);
  return (
    <ThemeContext.Provider value={value}>
      <Page />
    </ThemeContext.Provider>
  );
}

function Page() {
  const { theme } = useContext(ThemeContext);
  return <div className={theme}>...</div>;
}

What are custom Hooks and why write them?

A custom Hook is a function named useSomething that calls other Hooks. It extracts reusable stateful logic without forcing a shared component tree (unlike HOCs).

Examples: useFetch, useLocalStorage, useDebounce, useMediaQuery.

Each component that calls the custom Hook gets its own state — Hooks don't share state by calling the same function; they share the pattern.

function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn(v => !v), []);
  return [on, toggle];
}

Why do lists need keys, and why is using the array index often a bad idea?

Keys help React identify which items changed, were added, or removed during reconciliation.

Stable, unique IDs from your data are best. Index as key breaks when the list is reordered, filtered, or items inserted at the front — React reuses the wrong component instance (wrong local state, focus bugs).

Index keys are acceptable only for static lists that never reorder or grow/shrink in the middle.

items.map(item => (
  <TodoItem key={item.id} todo={item} />
));

Controlled vs uncontrolled components in forms.

Controlled — form value lives in React state; the input's value + onChange keep the DOM in sync. Full control for validation, disable submit, format-as-you-type.

Uncontrolled — the DOM owns the value; you read it via a ref (or FormData) on submit. Less re-renders; fine for simple forms.

Libraries like React Hook Form lean uncontrolled under the hood for performance, with controlled APIs when needed.

// Controlled
const [name, setName] = useState('');
<input value={name} onChange={e => setName(e.target.value)} />;

// Uncontrolled
const ref = useRef();
<input ref={ref} defaultValue="Ada" />;

What does "lifting state up" mean?

When two sibling components need the same data, move that state to their closest common parent and pass it down as props (and pass setters/callbacks to update it).

This keeps a single source of truth and avoids duplicated or out-of-sync state. If the tree gets deep, consider Context or a state library instead of prop-drilling through many layers.

What is prop drilling and how do you avoid it?

Prop drilling is passing props through many intermediate components that don't use them, only to reach a deep child.

Mitigations:

  • Component composition (children / slots)
  • Context for cross-cutting data
  • Colocate state closer to where it's used
  • State libraries (Zustand, Redux) for complex app state

What does React.memo do?

React.memo(Component) wraps a component so it skips re-render if props are shallowly equal to the previous props.

Useful for expensive pure children when the parent re-renders often. Combine with useCallback/useMemo so prop references stay stable. Custom comparison via the second argument is possible but easy to get wrong.

const Row = React.memo(function Row({ item }) {
  return <div>{item.name}</div>;
});

When would you choose useReducer over useState?

useReducer(reducer, initialState) centralizes complex state transitions: next state = reducer(state, action).

Prefer it when:

  • Multiple related fields update together
  • Next state depends on previous in non-trivial ways
  • You want testable, named actions (like a mini Redux)

For a single boolean or string, useState is clearer.

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'reset': return { count: 0 };
    default: return state;
  }
}
const [state, dispatch] = useReducer(reducer, { count: 0 });

What are Error Boundaries?

Error Boundaries are React components that catch JavaScript errors in their child tree during render/lifecycle, log them, and show a fallback UI instead of crashing the whole app.

They must be class components (or a library wrapper) implementing getDerivedStateFromError / componentDidCatch. They do not catch errors in event handlers, async code, or SSR — use try/catch there.

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  render() {
    if (this.state.hasError) return <h2>Something went wrong.</h2>;
    return this.props.children;
  }
}

How do React.lazy and Suspense help with code splitting?

React.lazy(() => import('./Page')) loads a component via dynamic import(), creating a separate bundle chunk.

Wrap lazy components in <Suspense fallback={...}> to show a loading UI while the chunk downloads. Common for route-based splitting (with React Router).

const Settings = React.lazy(() => import('./Settings'));

<Suspense fallback={<Spinner />}>
  <Settings />
</Suspense>

How does client-side routing work with React Router?

React Router maps URL paths to components without full page reloads. The browser History API updates the URL; React swaps the matched route element.

Core ideas: BrowserRouter, route config / Routes+Route, nested routes with Outlet, Link/NavLink, loaders/actions (data APIs in v6.4+), and protected routes via layout routes or loaders that check auth.

import { Routes, Route, Outlet } from 'react-router-dom';

function Layout() {
  return (
    <>
      <Nav />
      <Outlet />
    </>
  );
}

<Routes>
  <Route element={<Layout />}>
    <Route path="/" element={<Home />} />
    <Route path="users/:id" element={<User />} />
  </Route>
</Routes>

How do you choose between Context, Redux, and Zustand for state?

Local useState/useReducer — UI-only, one component or small subtree.

Context — low-frequency shared data (theme, auth). Avoid for chatty updates.

Redux Toolkit — large apps needing predictable updates, middleware, DevTools, normalized entities, time-travel debugging.

Zustand / Jotai / Recoil — lighter stores with less boilerplate; great default for many SPAs.

Also separate server state (TanStack Query / SWR) from client UI state — don't put API cache in Redux by default.

What are Synthetic Events in React?

React wraps native browser events in a SyntheticEvent for cross-browser consistency. You attach handlers with camelCase props (onClick, onChange).

In modern React, pooling is largely gone — you can use the event asynchronously. Still call e.preventDefault() / e.stopPropagation() as with native events. React delegates many events at the root for performance.

function Button() {
  function handleClick(e) {
    e.preventDefault();
    console.log(e.type);
  }
  return <button onClick={handleClick}>Save</button>;
}

What are Fragments and Portals?

Fragments (<>...</> or <React.Fragment>) group children without an extra DOM node. Useful when a component must return multiple siblings.

Portals (createPortal(child, domNode)) render children into a DOM node outside the parent hierarchy (modals, tooltips, toasts) while preserving React context and event bubbling in the React tree.

import { createPortal } from 'react-dom';

createPortal(<Modal />, document.getElementById('modal-root'));

What does React Strict Mode do?

<StrictMode> is a development-only helper that highlights unsafe patterns. In React 18+ it double-invokes certain functions (render, effects setup/cleanup) to surface missing effect cleanups and impure renders.

It does not affect production builds. Seeing effects run twice in dev is usually Strict Mode — not a production bug by itself.

import { StrictMode } from 'react';

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

useEffect vs useLayoutEffect — when do you need useLayoutEffect?

Both run after React updates the DOM in memory, but:

  • useEffect — after the browser paints (async). Default choice.
  • useLayoutEffect — synchronously after DOM mutations, before paint. Use when you must measure layout (getBoundingClientRect) or prevent a visible flicker by applying DOM updates before the user sees the frame.

Overusing useLayoutEffect blocks painting and hurts performance — prefer useEffect unless you see visual glitches.

useLayoutEffect(() => {
  const { height } = ref.current.getBoundingClientRect();
  setHeight(height);
}, [content]);

What are concurrent features and useTransition?

Concurrent rendering (React 18+) lets React interrupt, prioritize, and reuse work so the UI stays responsive.

useTransition marks state updates as non-urgent. Urgent updates (typing in an input) stay snappy while a heavy filtered list update can lag without blocking input.

useDeferredValue defers re-rendering with a lagging value of a prop/state for similar goals.

const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [list, setList] = useState(items);

function onChange(e) {
  setQuery(e.target.value); // urgent
  startTransition(() => {
    setList(filterHuge(e.target.value)); // non-urgent
  });
}

HOCs and render props vs Hooks — what changed?

HOCs (withAuth(Component)) and render props (<Data render={data => ...} />) were classic ways to reuse logic. They nest wrappers ("wrapper hell") and obscure the component tree.

Hooks extract the same logic into composable functions without changing the component hierarchy. Hooks are the preferred pattern for new code; you may still see HOCs in older codebases or libraries.

What are common patterns for conditional rendering in React?

Common patterns:

  • {cond && <Child />} — render when true (watch out for 0 rendering)
  • Ternary: {cond ? <A /> : <B />}
  • Early return: if (!data) return <Spinner />;
  • Switch / lookup maps for multiple views

Prefer clear early returns over deeply nested ternaries.

if (loading) return <Spinner />;
if (error) return <Error msg={error} />;
return <Profile user={user} />;

Why should render be pure, and what belongs in effects?

Component render should be a pure function of props/state: same inputs → same JSX, no side effects. Don't fetch, write localStorage, or mutate shared objects during render.

Side effects belong in event handlers (user-triggered) or useEffect (synchronize with external systems). Impure renders cause Strict Mode bugs, broken concurrent rendering, and hard-to-reproduce issues.

What are React Server Components (RSC) at a high level?

Server Components render on the server and send a serialized UI result to the client. They can access databases/files directly and ship zero (or less) JS for that subtree.

Client Components ('use client') run in the browser and can use state, effects, and event handlers.

Frameworks like Next.js App Router popularized RSC. Default to Server Components; push interactivity to Client Component leaves.

How do you typically test React components?

Prefer React Testing Library (RTL) with Jest/Vitest: render the component, query by role/label/text (how users see the UI), fire events, assert outcomes.

Avoid testing implementation details (internal state, CSS classes) when possible. Use MSW for API mocking. For E2E, Playwright or Cypress cover full flows.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('increments', async () => {
  render(<Counter />);
  await userEvent.click(screen.getByRole('button', { name: /add/i }));
  expect(screen.getByText('1')).toBeInTheDocument();
});

How do you diagnose and fix React performance issues?

Approach:

  1. Measure — React Profiler, Chrome Performance, why-did-you-render
  2. Cut unnecessary re-renders — state closer to consumers, memo children, stable callbacks
  3. Cut expensive work — useMemo for heavy calc, virtualize long lists (react-window / tanstack-virtual)
  4. Cut bundle size — lazy routes, tree-shakeable imports
  5. Concurrent tools — useTransition for heavy UI updates

Don't memoize blindly — fix state structure and list keys first.

Why is composition preferred over inheritance in React?

React favors composition: nest components, pass children, or use named slots/props that accept JSX. You reuse UI and behavior by combining pieces, not by extending class hierarchies.

Inheritance couples components and fights the function-component model. Patterns like compound components (Select + Select.Option) and children-as-API keep APIs flexible.

function Card({ title, children, actions }) {
  return (
    <section>
      <h2>{title}</h2>
      <div>{children}</div>
      <footer>{actions}</footer>
    </section>
  );
}