Modern React and React 19 Interview Questions: 10 Questions with Clear Answers
In front-end technical interviews, React 19 and modern component architecture represent the modern standard for mid-level and senior roles. Interviewers test whether candidates understand the paradigm shift brought by React Server Components, Server Actions, native action hooks, and compiler optimizations.
This guide covers 10 high-frequency Modern React and React 19 interview questions for developers with 0–5 years of experience.
Question Index
| # | Question | Focus Area | Difficulty |
|---|---|---|---|
| 1 | React Server Components (RSC) vs Client Components | Server/client boundaries, zero-bundle code, 'use client' | Junior-Mid |
| 2 | Server Functions & Actions with 'use server' | Server mutations, RPC architecture, serialization | Mid |
| 3 | Managing Form Lifecycle with useActionState | Native action states, pending status, error handling | Junior-Mid |
| 4 | Optimistic UI Updates with useOptimistic | Instant UI feedback, auto-rollback on server error | Mid |
| 5 | Reading Promises and Context with the use() Hook | Conditional context reading, Suspense streaming | Mid |
| 6 | Submitting Forms & useFormStatus | Native <form action>, nested pending status | Junior-Mid |
| 7 | How the React Compiler Eliminates Manual Memoization | Automated memoization vs useMemo / useCallback | Mid-Senior |
| 8 | Document Metadata Hoisting (<title>, <meta>) | Native head tag management without React Helmet | Junior-Mid |
| 9 | Resource Preloading APIs (preload, preinit) | Script and stylesheet prioritization in components | Mid-Senior |
| 10 | Suspense, Streaming SSR, and Selective Hydration | Chunked HTML streaming, independent hydration | Mid-Senior |
Q1. What is the difference between React Server Components (RSC) and Client Components, and what does the 'use client' directive actually do?
Level: Junior-Mid
What the interviewer is checking: Understanding component execution environments, client bundle size reduction, and dispelling the myth that 'use client' means a component runs exclusively on the browser.
Short answer to say out loud
"React Server Components run only on the server, sending zero JavaScript to the client and enabling direct backend access. Client Components provide browser interactivity and state. The 'use client' directive marks the entry boundary between server and client bundles—it signals to the bundler to include the file in client bundles, while still pre-rendering HTML on the server during SSR."
Detailed explanation
In the RSC architecture:
- Server Components (Default): Run on the server. They can be
asyncfunctions querying databases directly, sending 0KB of JavaScript to the client. They cannot use state, effects, or browser event listeners. - Client Components (
'use client'): Required for interactivity, state, effects, or browser APIs. 'use client'Directive: Marks the entry point boundary between server and client bundles. It signals to bundlers to include this module in the client JavaScript bundle. Client components still pre-render to HTML during SSR.
Example
// Server Component: direct async DB queries, 0KB client bundle
import db from '@/lib/db';
import { LikeButton } from './LikeButton'; // Client Component ('use client')
export async function PostFeed() {
const posts = await db.posts.findMany({ take: 5 });
return (
<div>
{posts.map(p => (
<div key={p.id}>
<h2>{p.title}</h2>
<LikeButton postId={p.id} initialLikes={p.likes} />
</div>
))}
</div>
);
}
Common mistakes
- Believing
'use client'makes a component run only on the client. It still pre-renders to HTML on the server during SSR. - Adding
'use server'at the top of a Server Component file. Server Components have no directive;'use server'is exclusively used to define Server Functions.
Likely follow-up questions
- Interviewer: "Can a Server Component be passed as a child to a Client Component?"
- Answer: "Yes. A Client Component can accept a Server Component as a
childrenprop:<ClientModal>{<ServerProfile />}</ClientModal>. The Server Component renders on the server and passes its rendered output (JSX) into the Client Component."
Q2. How do Server Functions and Server Actions work with the 'use server' directive, and how is security maintained across serialization boundaries?
Level: Mid
What the interviewer is checking: Understanding Remote Procedure Call (RPC) mechanics, client-server communication, argument serialization, and securing Server Functions against unauthorized execution.
Short answer to say out loud
"Server Functions ('use server') execute on the server but can be called from client components or forms via auto-generated POST endpoints. Security requires treating them as public APIs: always authenticate sessions inside the function, validate inputs with Zod, and ensure only serializable data crosses the network boundary."
Detailed explanation
The 'use server' directive marks asynchronous functions as callable server RPCs.
- In Server Components, they can be defined inline or imported.
- In Client Components, they must be imported from files marked
'use server'. - Calling them sends serialized arguments over an internal HTTP POST request, executes on the server, and returns serialized results.
Security: Every Server Function is a public network endpoint. Always authenticate the session and validate inputs with schemas (like Zod) inside the function.
Example
'use server';
import db from '@/lib/db';
import { auth } from '@/lib/auth';
import { z } from 'zod';
const Schema = z.object({ name: z.string().min(2) });
export async function updateUserName(formData: FormData) {
const session = await auth();
if (!session?.user?.id) throw new Error('Unauthorized');
const { name } = Schema.parse({ name: formData.get('name') });
await db.user.update({ where: { id: session.user.id }, data: { name } });
return { success: true };
}
Common mistakes
- Treating Server Functions as private internal code and skipping authentication or authorization checks.
- Passing non-serializable values (functions, classes, DOM nodes) as arguments or return values.
Likely follow-up questions
- Interviewer: "What is the difference between a Server Function and a Server Action?"
- Answer: "All functions with
'use server'are Server Functions. When a Server Function is passed to a<form action={...}>prop or invoked inside a transition, it acts as a Server Action, participating in automatic pending states and revalidation."
Q3. How does the useActionState hook simplify form state management in React 19?
Level: Junior-Mid
What the interviewer is checking: Familiarity with React 19's native action state handling, eliminating redundant useState / useEffect patterns for form submissions, and progressive enhancement.
Short answer to say out loud
"useActionState manages state based on the result of a form action, returning [state, formAction, isPending]. It replaces manual useState loading and error boilerplate, managing pending transitions automatically and supporting progressive enhancement."
Detailed explanation
useActionState unifies submission loading, error, and return states:
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);
fn: Async function(previousState, formData) => nextState.state: Latest result returned byfn.formAction: Dispatcher passed to<form action={formAction}>.isPending: Boolean tracking whether the transition is running. If apermalinkis supplied, the form supports progressive enhancement before client JavaScript downloads.
Example
'use client';
import { useActionState } from 'react';
import { updateProfile } from '@/app/actions';
export function ProfileForm() {
const [state, formAction, isPending] = useActionState(
async (prev, formData: FormData) => {
try {
await updateProfile(formData);
return { error: null, ok: true };
} catch (e) {
return { error: (e as Error).message, ok: false };
}
},
{ error: null, ok: false }
);
return (
<form action={formAction}>
<input name="username" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{state.error && <p className="error">{state.error}</p>}
</form>
);
}
Common mistakes
- Forgetting that the action function receives
(previousState, formData)as its parameters, leading to swapped arguments. - Manually toggling an external
setIsLoading(true)state whenisPendingalready provides automatic transition tracking.
Likely follow-up questions
- Interviewer: "What was
useActionStatecalled in earlier React Canary releases?" - Answer: "It was originally introduced as
useFormStateinreact-dom, but was renamed touseActionStatein React 19 and moved to the corereactpackage to support both form and non-form actions."
Q4. How do you implement optimistic UI updates with useOptimistic in React 19?
Level: Mid
What the interviewer is checking: Understanding optimistic rendering patterns, immediate feedback during async mutations, and automatic state reconciliation or rollback on failure.
Short answer to say out loud
"useOptimistic displays immediate predicted state during in-flight asynchronous actions. Calling setOptimistic(tempVal) updates the UI instantly; once the server action settles, React drops the temporary state and shows confirmed data, automatically rolling back on error."
Detailed explanation
useOptimistic manages optimistic UI lifecycles during Actions:
const [optimistic, setOptimistic] = useOptimistic(state, updateFn).- Call
setOptimistic(tempVal)inside an Action to immediately render predicted feedback. - When the async action settles, React drops the optimistic state and displays confirmed server data.
- If the action throws, React automatically rolls back the optimistic state without manual rollback code.
Example
'use client';
import { useOptimistic } from 'react';
import { sendMessage } from '@/app/actions';
export function Chat({ messages }: { messages: Array<{ id: string; text: string }> }) {
const [optimisticList, addOptimistic] = useOptimistic(
messages,
(state, newText: string) => [...state, { id: 'temp', text: newText, pending: true }]
);
return (
<form action={async (formData) => {
const text = String(formData.get('message'));
addOptimistic(text);
await sendMessage(text);
}}>
<ul>
{optimisticList.map(m => (
<li key={m.id} style={{ opacity: (m as any).pending ? 0.5 : 1 }}>{m.text}</li>
))}
</ul>
<input name="message" required />
<button type="submit">Send</button>
</form>
);
}
Common mistakes
- Calling
setOptimisticoutside of a Transition or Form Action, which triggers a runtime error:An optimistic state update occurred outside a Transition or Action. - Mutating state directly inside the optimistic reducer instead of returning a new immutable state copy.
Likely follow-up questions
- Interviewer: "How does React know when to discard the optimistic state?"
- Answer: "Optimistic updates are scoped to the lifetime of the surrounding async Transition or Action. Once the transition promise settles, React automatically re-renders with the confirmed state."
Q5. What is the use() API in React 19, and how does it differ from traditional hooks?
Level: Mid
What the interviewer is checking: Understanding how use() breaks the classic Rules of Hooks by allowing conditional invocation, promise unwrapping with Suspense, and reading Context dynamically.
Short answer to say out loud
"The use() API unwraps Promises and Contexts conditionally inside if statements and loops. With Promises, it suspends rendering until resolution, triggering the nearest Suspense boundary. With Context, it provides a conditional alternative to useContext."
Detailed explanation
The use() API unwraps Promises and Contexts conditionally inside if statements and loops.
- Unwrapping Promises: Calling
use(promise)suspends component rendering until the promise resolves, triggering the nearest<Suspense>fallback. - Reading Context: Calling
use(ThemeContext)allows reading context values conditionally. Promises must be created outside render (passed from Server Components or cached) to avoid infinite suspension loops.
Example
'use client';
import { use, Suspense } from 'react';
interface UserData {
name: string;
email: string;
}
// Component receives a Promise passed from a Server Component
function UserDetails({ userPromise }: { userPromise: Promise<UserData> }) {
// Unwraps the promise value; suspends if pending
const user = use(userPromise);
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
export function UserProfileWrapper({ userPromise }: { userPromise: Promise<UserData> }) {
return (
<Suspense fallback={<p>Loading user details...</p>}>
<UserDetails userPromise={userPromise} />
</Suspense>
);
}
Common mistakes
- Creating a new Promise directly inside the component body before passing it to
use()(const p = fetch(...); use(p);). Every re-render creates a new pending promise, causing an infinite loading loop. - Using
use()inside a Server Component instead of simpleawait. In Server Components, nativeasync/awaitis preferred.
Likely follow-up questions
- Interviewer: "Can
use()be called inside atry...catchblock?" - Answer: "No.
use()must not be called insidetry...catchbecause React relies on thrown promises to trigger Suspense boundaries. Wrappinguse()intry...catchcatches the suspended promise and breaks Suspense."
Q6. How does useFormStatus work, and why must it be called from a child component of the form?
Level: Junior-Mid
What the interviewer is checking: Understanding React 19's form context propagation, reading parent form submission status, and building self-contained submit buttons.
Short answer to say out loud
"useFormStatus reads status (pending, data) from an enclosing parent <form> via context, allowing submit buttons to show loading states without prop drilling. It must be called from a child component inside the form."
Detailed explanation
useFormStatus reads status from an enclosing parent <form> via context:
const { pending, data, method, action } = useFormStatus();
pending: True while the parent form action is executing.data:FormDatainstance of the active submission. Limitation: It must be called from a child component rendered inside the<form>. Calling it in the same component that renders<form>returnspending: false."
Example
'use client';
import { useFormStatus } from 'react-dom';
// 1. Child Submit Button reads parent form context directly
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} className="btn-primary">
{pending ? 'Saving...' : 'Save Changes'}
</button>
);
}
// 2. Parent Form renders the child button
export function SettingsForm({ saveAction }: { saveAction: (data: FormData) => Promise<void> }) {
return (
<form action={saveAction}>
<input name="displayName" placeholder="Display Name" required />
{/* SubmitButton receives pending status automatically */}
<SubmitButton />
</form>
);
}
Common mistakes
- Calling
useFormStatusinside the component that renders<form>rather than inside a child component, resulting inpendingalways beingfalse. - Assuming
useFormStatusworks with custom non-action form handlers (onSubmit={handleSubmit}) that don't use React 19 Actions.
Likely follow-up questions
- Interviewer: "How can a submit button know which form fields are being submitted before the action finishes?"
- Answer: "Inspect the
dataobject returned byuseFormStatus().data.get('fieldName')exposes the values being submitted in real time."
Q7. What is the React Compiler (React Forget), and how does it fundamentally change performance optimization in modern React?
Level: Mid-Senior
What the interviewer is checking: Awareness of React's architectural evolution, compiler-driven automatic memoization, and the deprecation of manual optimization boilerplate (useMemo, useCallback, React.memo).
Short answer to say out loud
"The React Compiler is an automated build-time tool that memoizes values, callback functions, and JSX elements automatically. It eliminates manual useMemo, useCallback, and React.memo by analyzing JavaScript mutation semantics to preserve object identity when dependencies are unchanged."
Detailed explanation
The React Compiler automatically memoizes component values, functions, and JSX trees at build time.
- Eliminates manual
useMemo,useCallback, andReact.memoboilerplate. - Understands JavaScript mutation semantics to cache expressions and preserve object identities when dependencies are unchanged.
- Validates code against the Rules of React, safely falling back to standard rendering if impure mutations are detected.
Example
// Idiomatic React 19 with React Compiler: no useMemo, useCallback, or memo
export function ProductCatalog({ products, filterText, onSelect }) {
const filtered = products.filter(p => p.name.includes(filterText));
const handleClick = (id: string) => onSelect(id);
return <ItemList items={filtered} onItemClick={handleClick} />;
}
Common mistakes
- Assuming the React Compiler can fix impure components. If a component mutates global variables or modifies props during render, the compiler bails out or exhibits unexpected behavior.
- Continuing to defensively add
useCallbackeverywhere in new React 19 projects configured with the compiler.
Likely follow-up questions
- Interviewer: "How does the React Compiler verify that components are safe to optimize?"
- Answer: "The compiler validates components against the Rules of React using static analysis and ESLint's
eslint-plugin-react-compiler. If a component violates purity or hook rules, the compiler safely skips optimizing that specific component and falls back to standard rendering."
Q8. How does React 19 natively handle Document Metadata (<title>, <meta>, <link>) without third-party libraries?
Level: Junior-Mid
What the interviewer is checking: Understanding native head tag hoisting, eliminating libraries like react-helmet, and search engine optimization (SEO) in modern React.
Short answer to say out loud
"React 19 natively supports <title>, <meta>, and <link> tags in any component, automatically hoisting them into <head> during SSR and client navigation. Tags deduplicate automatically by attributes, eliminating third-party libraries like react-helmet."
Detailed explanation
React 19 natively hoists <title>, <meta>, and <link> tags from any component into the document <head>:
- Streams hoisted metadata inside
<head>during SSR for search crawlers. - Reconciles and updates title and meta tags during client-side navigation.
- Deduplicates tags automatically based on
name,property, andhrefattributes, eliminating the need forreact-helmet.
Example
export function BlogPost({ post }: { post: { title: string; summary: string; slug: string } }) {
return (
<article className="post-container">
{/* React 19 automatically hoists these tags into document <head> */}
<title>{post.title} | Daily Tech Feeds</title>
<meta name="description" content={post.summary} />
<meta property="og:title" content={post.title} />
<link rel="canonical" href={`https://example.com/posts/${post.slug}`} />
<h1>{post.title}</h1>
<p>{post.summary}</p>
</article>
);
}
Common mistakes
- Installing
react-helmetin React 19 projects, which causes redundant DOM head mutations and conflicts with React's native hoister. - Rendering dynamic metadata in raw client effects (
document.title = ...) which fails to provide SEO metadata to search crawlers during initial SSR.
Likely follow-up questions
- Interviewer: "How does React 19 handle precedence when multiple nested components render a
<title>tag?" - Answer: "The innermost component's
<title>tag takes precedence, allowing a child route or detail view to override the default title provided by an outer layout."
Q9. How do React 19's resource loading APIs (preload, preinit, preconnect) optimize asset delivery?
Level: Mid-Senior
What the interviewer is checking: Understanding browser asset prioritization, programmatic resource preloading from component code, and avoiding stylesheet flash (FOUC).
Short answer to say out loud
"React 19 provides programmatic asset functions in react-dom (preload, preinit, preconnect) to discover and load fonts, stylesheets, and scripts early during render. Preinitializing stylesheets guarantees insertion before paint, eliminating visual layout shifts."
Detailed explanation
React 19 introduces programmatic resource functions in react-dom:
preconnect(origin): Early DNS and TLS handshakes.preload(href, options): Early downloads for fonts, scripts, or images.preinit(href, options): Loads and executes external stylesheets or scripts immediately.- Stylesheet hoisting (
<link rel="stylesheet" precedence="high" />) hoists styles to<head>and avoids layout shifts.
Example
import { preload, preinit } from 'react-dom';
export function DataVisualization() {
// Preload critical charting font and preinit stylesheet before rendering chart
preload('/fonts/chart-icons.woff2', { as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' });
preinit('/styles/charts.css', { as: 'style' });
return (
<section className="visualization">
<h2>Quarterly Revenue</h2>
<div id="chart-mount" />
</section>
);
}
Common mistakes
- Using
preloadinsideuseEffect.useEffectruns only after initial paint, defeating the purpose of preloading assets early. Resource functions should be called during render. - Preloading low-priority resources that compete for network bandwidth against the critical rendering path.
Likely follow-up questions
- Interviewer: "What does the
precedenceattribute on a<link rel='stylesheet'>tag do in React 19?" - Answer: "
precedenceestablishes an ordering hierarchy (e.g.precedence='high'). React sorts stylesheets in the<head>by precedence so component styles cascade predictably regardless of render order."
Q10. How does Suspense with Streaming SSR and Selective Hydration improve Core Web Vitals?
Level: Mid-Senior
What the interviewer is checking: Comprehension of modern full-stack rendering architectures, HTTP chunked streaming, resolving TTFB/LCP bottlenecks, and progressive interactivity.
Short answer to say out loud
"Streaming SSR streams shell HTML immediately to optimize TTFB and FCP, streaming slow Suspense subtrees as chunked HTML once data resolves. Selective Hydration prioritizes hydrating components users interact with first, eliminating input delay and optimizing INP."
Detailed explanation
Suspense and Streaming SSR resolve traditional SSR bottlenecks:
- HTML Streaming: Server streams the shell HTML immediately via chunked transfer, optimizing TTFB and FCP.
- Out-of-Order Streaming: Suspended components stream HTML chunks as data resolves, replacing skeletons on the fly.
- Selective Hydration: React hydrates components independently. If a user clicks an unhydrated component, React prioritizes hydrating that subtree first, optimizing INP.
Example
import { Suspense } from 'react';
export default function Page({ id }: { id: string }) {
return (
<main>
<Header />
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails id={id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews id={id} />
</Suspense>
</main>
);
}
Common mistakes
- Putting a single
<Suspense>wrapper around the entire page, reverting streaming back to an all-or-nothing bottleneck. - Forgetting that selective hydration requires splitting client code with dynamic imports (
React.lazyor framework code splitting) to yield hydration priority.
Likely follow-up questions
- Interviewer: "What happens if JavaScript fails to load or is disabled in a Streaming SSR application?"
- Answer: "Because the server streams real HTML chunks for the suspended content once resolved, users still see the complete rendered page content, preserving accessibility and SEO even without client JavaScript."
Preparation Checklist
Review this actionable preparation checklist before your interview:
- Master RSC vs Client Components: Articulate that
'use client'defines a bundler boundary, not a client-only execution rule. - Secure Server Functions: Treat
'use server'as public endpoints; validate inputs with Zod and authenticate sessions. - Leverage
useActionState: Replace submission state boilerplate with native action state and pending indicators. - Implement optimistic UI: Use
useOptimisticfor instant feedback with automatic error rollback. - Unwrap resources with
use(): Understand howuse(Promise)triggers Suspense and howuse(Context)reads conditionally. - Explain the React Compiler: Detail how automated build-time memoization eliminates
useMemoanduseCallback. - Use native head tags: Render
<title>and<meta>tags directly in components withoutreact-helmet. - Detail Selective Hydration: Explain how chunked streaming SSR and priority hydration optimize TTFB and INP.
Conclusion
Modern React has transitioned from client-side DOM reconciliation to a unified full-stack component architecture. Mastering React Server Components, action hooks, compiler memoization, and streaming SSR proves that your skills reflect modern production standards and senior architectural thinking.