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.
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.
Same type, update in place. React keeps the DOM node and updates only the attributes that changed, then recurses into the children.
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.
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?
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.
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?
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.
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?
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.
Rendered
Best for
Cost
CSR
In the browser, after the bundle loads
Dashboards behind a login
Slow first paint, weak crawlability
SSR
On the server, per request
Personalised or fast changing pages
Server load and latency per request
SSG
At build time
Marketing, docs, blogs
Content is only as fresh as the last build
ISR
At build, then re-generated on a schedule
Large catalogues that change occasionally
Some 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.
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.