Advanced React Interview Questions and Answers

Fiber, reconciliation, concurrent rendering, Suspense, error boundaries, portals, Server Components, hydration and rendering strategies for senior interviews.

16 questions6 mid-level, 10 senior

These are the questions that separate a senior interview from a mid-level one. They are less about API recall and more about the model: what React is doing between your state update and the pixels, and what changes when part of the work moves to the server.

You will not be expected to have implemented any of this. You will be expected to explain the problem each thing was built to solve, and to be honest about where the boundaries of your knowledge are.

1.What is reconciliation, and what heuristics does React's diffing algorithm use?

Senior

Reconciliation is how React works out the difference between the previous element tree and the new one. A general tree diff is O(n³), so React uses two assumptions to make it O(n): different element types produce different trees, and keys identify children that stayed the same.

  1. Different types, different trees. If an element changes from div to span, or from one component type to another, React destroys the old subtree, including its state, and builds the new one. It does not try to match their contents.
  2. Same type, update in place. React keeps the DOM node and updates only the attributes that changed, then recurses into the children.
  3. Keys identify children across renders. Within a list, a key tells React that an item is the same item even though its position changed.

That is also the mechanism behind the key reset trick: change a component's key and, by rule three, React treats it as a different child and mounts a fresh instance.

Likely follow-up questions

  • Why is a general tree diff O(n³)?
  • What happens to state when a component's type changes?

2.What is React Fiber?

Senior

Fiber is the reconciler React has used since version 16. It re-implemented rendering as a linked list of units of work that can be paused, resumed, reprioritised and abandoned, which is what makes concurrent features possible.

The old reconciler walked the tree with recursion. Once it started it could not stop, so a large update blocked the main thread until it finished and the page froze. Fiber turned that recursion into a loop over a data structure, so React can do a slice of work, hand control back to the browser, and continue afterwards.

  • A fibre node holds a component's type, props, state and pointers to its child, sibling and parent.
  • Work is split into two phases: a render phase that can be interrupted and produces a list of changes, and a commit phase that applies them and cannot be interrupted.
  • Updates carry a priority, so a keystroke can jump ahead of rendering a large list.
  • React keeps two trees, current and work-in-progress, and swaps them on commit. That double buffering is what allows an in-progress render to be thrown away safely.

Likely follow-up questions

  • Why does the render phase have to be pure?
  • What is double buffering here?

3.What is concurrent rendering in React 18?

Senior

Concurrent rendering lets React prepare more than one version of the UI at once and interrupt a low priority render when something more urgent arrives. It is not a feature you turn on, it is a capability that features such as transitions and Suspense build on.

  • Interruptible rendering. A slow render caused by typing in a filter can be abandoned when the next keystroke arrives.
  • Automatic batching. Since React 18, multiple state updates are batched into one render even inside promises, timeouts and native event handlers.
  • Transitions. startTransition marks an update as non-urgent so React can keep the input responsive.
  • Selective hydration. A server rendered page can hydrate the part the user just interacted with first.

Likely follow-up questions

  • Is concurrent React multithreaded?
  • What is automatic batching and what changed in React 18?

4.What are error boundaries, and what do they not catch?

Mid-level

An error boundary is a component that catches errors thrown while rendering its subtree and shows a fallback instead of unmounting the whole app. It does not catch errors in event handlers, in asynchronous code, during server rendering, or thrown by the boundary itself.

Still a class: there is no hook equivalent.
class ErrorBoundary extends React.Component {
  state = { error: null };

  static getDerivedStateFromError(error) {
    return { error };          // render the fallback
  }

  componentDidCatch(error, info) {
    logToService(error, info); // report it
  }

  render() {
    return this.state.error
      ? this.props.fallback
      : this.props.children;
  }
}

Errors in an event handler are not thrown during rendering, so React never sees them: use try/catch there and put the failure into state. In practice most teams use react-error-boundary, which wraps the class and adds a reset function.

  • Place boundaries around independent regions, so a broken widget does not blank the page.
  • An uncaught render error in React 16 and later unmounts the entire tree, which is why having at least one boundary matters.
  • Give the fallback a way to recover, usually a retry that resets the boundary's state.

Likely follow-up questions

  • Why can an error boundary not be a function component?
  • How would you catch an error in an event handler?
Practise this in the quiz

5.What are portals and when do you need one?

Mid-level

createPortal renders children into a DOM node outside the parent's hierarchy while keeping them in the React tree. You need it when an ancestor's overflow, z-index or transform would clip or trap a modal, tooltip or dropdown.

function Modal({ children, onClose }) {
  return createPortal(
    <div className='overlay' onClick={onClose}>
      <div className='dialog' role='dialog' aria-modal='true'>{children}</div>
    </div>,
    document.body
  );
}

A portal solves the layout problem and none of the accessibility problems. You still have to trap focus inside the dialog, restore it on close, close on Escape, and mark the rest of the page as inert.

Likely follow-up questions

  • Do events bubble out of a portal?
  • What do you still have to handle yourself in a modal?
Practise this in the quiz

6.What is Suspense and how does it work?

Mid-level

Suspense lets a component tell React it is not ready, and React shows the nearest boundary's fallback until it is. It powers lazy loaded components, and with a data source that integrates with it, data fetching too.

<Suspense fallback={<ProfileSkeleton />}>
  <Profile userId={id} />
</Suspense>
  • The mechanism is a thrown promise: a suspending component throws, React catches it, renders the fallback, and retries when the promise settles.
  • Boundaries nest, so you can show a coarse skeleton for the page and finer ones for slower regions.
  • Content already on screen is not replaced by a fallback if the update is wrapped in startTransition, which prevents the jarring flash back to a spinner.
  • Suspense handles the pending state only. Failure is an error boundary's job, so the two are usually paired.

The value over an isLoading flag is that the loading state is declared where the layout is, not threaded through every component, and that React coordinates several suspending children into a single fallback.

Likely follow-up questions

  • Can you use Suspense for data fetching in a plain client app?
  • What happens if a suspending component throws an error instead?
Practise this in the quiz

7.What is the difference between CSR, SSR, SSG and ISR?

Mid-level

CSR renders in the browser from an empty shell. SSR renders HTML per request. SSG renders HTML at build time. ISR is SSG that regenerates pages in the background after a set interval.

RenderedBest forCost
CSRIn the browser, after the bundle loadsDashboards behind a loginSlow first paint, weak crawlability
SSROn the server, per requestPersonalised or fast changing pagesServer load and latency per request
SSGAt build timeMarketing, docs, blogsContent is only as fresh as the last build
ISRAt build, then re-generated on a scheduleLarge catalogues that change occasionallySome requests see slightly stale content

The interviewer usually wants you to pick per route rather than per app. A product page can be static, its stock indicator can be client rendered, and the account page can be server rendered. Saying that explicitly is a stronger answer than defending one strategy.

Likely follow-up questions

  • Which would you use for a product listing page?
  • What is hydration and where does it fit in?

8.What is hydration, and what causes a hydration mismatch?

Senior

Hydration is React attaching event listeners and internal state to server rendered HTML instead of recreating it. A mismatch happens when the first client render produces different markup from the server's, usually because of time, randomness or browser-only APIs.

  • new Date(), Math.random() or a counter produce different values in the two environments.
  • Reading window, localStorage or navigator during render: they do not exist on the server.
  • Markup the browser corrects, such as a div inside a p, which changes the DOM before React sees it.
  • Locale or timezone formatting that differs between the server and the user's machine.
Render the same thing on both sides, then update after mount.
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);

return <span>{mounted ? formatLocalTime(value) : formatUtc(value)}</span>;

Likely follow-up questions

  • Why is a mismatch worse than a slower render?
  • What is selective hydration?

9.What are React Server Components?

Senior

Server Components run only on the server and never ship to the browser. They can await data directly and render to a serialised description that the client merges into its tree, so their dependencies add nothing to the bundle.

No effect, no loading state, no client bundle cost.
// app/products/page.jsx - a Server Component by default in Next.js
export default async function ProductsPage() {
  const products = await db.product.findMany();
  return <ProductGrid products={products} />;
}
  • They cannot use state, effects, refs or browser APIs, because none of that exists on the server.
  • Anything interactive is a Client Component, marked with 'use client', and Server Components can render Client Components as children.
  • A heavy dependency such as a markdown or syntax highlighting library used only in a Server Component costs the user nothing.
  • They are not the same as SSR. SSR renders your client components to HTML and then hydrates them; Server Components never hydrate at all.

Likely follow-up questions

  • How is this different from SSR?
  • How do a Server Component and a Client Component pass data to each other?

10.What are Server Actions?

Senior

A Server Action is a function marked 'use server' that a client component can call as if it were local; the framework turns the call into a request. It replaces hand-written API routes for mutations and works as a form action even before JavaScript loads.

'use server';

export async function createTodo(formData) {
  await db.todo.create({ data: { title: formData.get('title') } });
  revalidatePath('/todos');
}

// In a component
<form action={createTodo}>...</form>

The security point is the one to raise unprompted: a Server Action is a public endpoint. The fact that it is called from one component does not restrict who can call it, so it must authenticate and validate its input exactly like any other route handler.

Likely follow-up questions

  • Are Server Actions secure by default?

11.What is streaming SSR and selective hydration?

Senior

Streaming sends the HTML in chunks as it is rendered, so the shell appears while slow parts are still pending inside Suspense boundaries. Selective hydration then hydrates the parts the user interacts with first, rather than the whole page in order.

Traditional SSR was all or nothing twice over: the server had to finish the entire page before sending anything, and the client had to hydrate the entire page before anything was interactive. One slow database query blocked both.

  • renderToPipeableStream sends the shell immediately and streams each Suspense boundary's HTML as its data resolves.
  • React hydrates boundaries independently, and prioritises the one the user just clicked, replaying the event once it is ready.
  • This is the mechanism behind the Next.js app router's loading.js and streamed layouts.

Likely follow-up questions

  • What is Time to Interactive and how does this change it?

12.How do you handle errors that error boundaries cannot catch?

Mid-level

Catch them where they happen. Wrap event handlers and async work in try/catch and move the failure into state, use the query library's error state for data fetching, and add window listeners for error and unhandledrejection as a last resort.

async function handleSave() {
  try {
    setStatus('saving');
    await save(values);
    setStatus('saved');
  } catch (error) {
    report(error);
    setError('We could not save your changes. Please try again.');
    setStatus('idle');
  }
}
  • Rendering an error thrown in a handler by putting it into state is a neat trick: it moves the error into the render phase where a boundary can catch it.
  • window.addEventListener('unhandledrejection', ...) catches promises nobody handled, which is worth reporting even if you cannot recover.
  • Distinguish expected failures, such as a validation error, from unexpected ones. Only the second kind belongs in a crash reporter.

Likely follow-up questions

  • What would you show the user in each case?

13.How would you structure a large React application?

Senior

Group by feature rather than by file type, keep shared UI and utilities in a small common layer, and enforce the direction of dependencies so features do not import each other's internals.

Feature folders, with a public entry point per feature.
src/
  features/
    checkout/
      components/
      hooks/
      api.ts
      index.ts        <- the only thing other features may import
    catalogue/
  shared/
    ui/               <- buttons, inputs, layout primitives
    lib/              <- formatting, dates, fetch wrapper
  app/                <- routing, providers, entry point
  • components/, hooks/ and utils/ folders at the top level stop scaling early: a change to one feature touches four directories.
  • Features may depend on shared, never the other way round, and ideally not on each other. A lint rule can enforce this.
  • Colocate tests, styles and types with the code they belong to.
  • Keep the routing layer thin: routes compose features, they do not contain logic.

Likely follow-up questions

  • How do you stop two features importing each other?
  • Where do shared types live?

14.What is the difference between React and Next.js?

Mid-level

React is the UI library. Next.js is a framework built on React that adds routing, server rendering and static generation, data fetching conventions, bundling, image optimisation and an API layer.

  • React leaves routing, rendering strategy, bundling and data fetching to you. Next.js decides them, which is why an app can be productive on day one.
  • Next.js supports static generation, server rendering, incremental regeneration and client rendering per route.
  • It is where React Server Components and Server Actions are actually usable today.
  • The cost is convention and lock-in: you are inside the framework's model for routing, caching and deployment.

A good closing line: choose plain React with Vite for an app behind a login where SEO and first paint do not matter, and a framework for anything public facing or content heavy.

Likely follow-up questions

  • When would you not use a framework?

15.How would you handle authentication in a React application?

Senior

Keep tokens out of localStorage where possible: prefer httpOnly, secure, sameSite cookies set by the server. Hold the session in context, guard routes for user experience, and treat every authorisation decision as the server's job.

  • Storage. A token in localStorage is readable by any script on the page, so a single XSS becomes full account takeover. An httpOnly cookie is not reachable from JavaScript.
  • Refresh. Keep access tokens short lived and refresh them through an endpoint; queue requests that arrive during a refresh so they are retried rather than failing.
  • Client state. An AuthContext exposing user, isLoading, signIn and signOut is enough for the UI.
  • Routing. Guard routes so users are not shown pages that will fail, and handle the loading state so nobody is bounced to login mid-check.
  • Authorisation. Hiding a button is presentation. The API must enforce permissions on every request.

Likely follow-up questions

  • Where would you store a JWT and why?
  • How do you handle token refresh with concurrent requests?

16.How would you build a reusable component library?

Senior

Start from accessible primitives, design the API around composition rather than configuration props, expose styling hooks instead of hard-coding a theme, and document with real examples. Version it and treat prop changes as breaking changes.

  • Do not write a combobox from scratch. Build on an accessible headless library and put your design on top; the accessibility surface of a menu or a dialog is much larger than it looks.
  • Composition over configuration. <Dialog><Dialog.Title/></Dialog> ages better than a Dialog with twenty props.
  • Forward the escape hatches. Accept className, spread the rest of the props onto the underlying element, and forward the ref. A component nobody can adjust gets copied instead of used.
  • Theme with CSS custom properties or tokens, so consumers restyle without forking.
  • Document with a live sandbox, and test the behaviour, not the markup.

Interviewers ask this to hear whether you think about the people consuming your code. The strongest signal is that you mention escape hatches and versioning, because both come from having maintained a library that other teams depend on.

Likely follow-up questions

  • How do you handle a breaking change?

All React interview questions