A React Server Component can be declared as an `async function` and `await` data directly in its body. What does React do with that, and why can a Client Component not be written the same way?
answer
- the component itself can wait
- no loading flag in the body
- suspends one subtree, not the page
- the client has no async components
basics
~20 sReact awaits the promise as part of rendering that subtree and renders the component once the data arrives, so no loading flag or effect is needed. On the client, React 19 does not support async function components.
solid answer
~40 sOn the server, rendering is allowed to take time. When a Server Component is an `async function`, React treats the returned promise as part of rendering that subtree: it pauses that branch, keeps working elsewhere, and emits the component's output once the promise resolves. The waiting is covered by the nearest `Suspense` boundary above it, so you write the happy path only — no `isLoading` state, no effect, no early return. A Client Component cannot do this: React 19 does not support async function components in the browser, because a client render must be able to produce something synchronously and be re-run at any time. In a Client Component you read a promise with `use()` or hand the problem to a data library instead.
code
jsx · 12 linesexport default async function Invoice({ id }) {
const res = await fetch(`https://api.example.com/invoices/${id}`);
const invoice = await res.json();
return (
<dl>
<dt>Number</dt>
<dd>{invoice.number}</dd>
<dt>Total</dt>
<dd>{invoice.total}</dd>
</dl>
);
}go deeper
Know that a Server Component may be written as an async function and await its data directly, and that you do not write loading state inside it.
Explain the mechanism: React makes the promise part of rendering that subtree, the pending period surfaces at the nearest Suspense boundary, and async function components are server-only in React 19.
Demonstrate waterfall judgment — distinguish sibling awaits that overlap from nested data dependencies that stack, and show how you hoist or pass a promise down to flatten them.
Own the architectural consequence: awaiting where the data lives removes endpoints that existed only to feed components, and you should be able to weigh that against the coupling it introduces between UI structure and data access.
## What `async` buys you on the server A Server Component runs once, during a render that is allowed to take milliseconds or hundreds of milliseconds. That makes it safe for the component itself to be asynchronous: ```jsx export default async function Invoice({ id }) { const invoice = await db.invoices.find(id); return <h1>Total: {invoice.total}</h1>; } ``` React sees that this component returned a promise and treats it as part of rendering that branch. It does not block the whole tree: other branches keep rendering, and when the promise resolves React continues with the returned JSX and emits that part of the output. The important consequence is what *disappears* from your code. There is no `loading` state, no effect to kick the request off after mount, no `if (!data) return <Spinner/>` guard, and no second render pass to fold the data in. The component reads like synchronous code that happens to await. ## Where the waiting shows up The pending period is not invisible — it is handled by the nearest `Suspense` boundary above the component. That boundary's fallback stands in for the subtree until the data lands. Placing those boundaries is a design decision in its own right; the point here is simply that the async component does not manage its own loading UI, the boundary does. ## Data locality is the second payoff Because the await happens on the server, what it awaits can be server-only: a database driver, a filesystem read, an internal service called with a credential from the environment. None of that is reachable from the browser, and none of it appears in the bundle. Contrast the pre-RSC shape, where the browser had to call an HTTP endpoint that existed purely so a component could get its data. ## Why the client side is different React 19 does not support `async function` components on the client. The reason is structural rather than arbitrary: - A client render can be **re-run**, discarded, and re-run again — React may render speculatively at a lower priority and throw the work away. Restarting an `async` function body is not something React can do safely; side effects inside it would already have happened. - A client render is **interruptible and resumable** in a way that a suspended promise chain in an arbitrary function body is not. - The browser needs to paint *something*, so the model is: render synchronously, and let a suspended read raise to the nearest boundary. So in a Client Component you do not `await` in the body. You read an already-created promise with `use()`, or you use a data-fetching library that manages the request and gives you a value plus status. Note the direction that implies: the promise should usually be created outside the render (or on the server and passed in), not created fresh inside the component body — a promise created during render is a new promise on every render. ```jsx 'use client'; // Invalid in React 19: async function components are server-only. export default async function Broken() { const data = await fetch('/api/x').then((r) => r.json()); return <p>{data.label}</p>; } ``` ## Waterfalls: the part interviewers push on A common follow-up is whether awaiting in components serialises everything. It does not, for siblings: React starts rendering sibling components without waiting for one another, so two sibling components each awaiting a 300 ms request cost roughly 300 ms together, not 600 ms. The genuine waterfall is **nesting with a data dependency**: a parent awaits, then renders a child that only then starts its own request. Those latencies add. The fixes are the ordinary ones — start both requests at the same level and await them together, or start the request earlier and pass the promise down so the consumer awaits an already-in-flight operation rather than kicking off a new one. ## What a strong answer sounds like Name the mechanism (React makes the promise part of rendering that subtree, and the wait surfaces at the nearest Suspense boundary), name what it removes (loading state, fetch-in-effect, an HTTP hop that existed only to feed a component), and name the asymmetry (async components are server-only in React 19; the client reads resources instead). Finish with the waterfall nuance, because that is where the conversation usually goes next.
- What does the rest of the page do while an async Server Component is awaiting its data?It carries on. React renders everything it can and suspends only that branch; the nearest Suspense boundary above the component shows its fallback, and the finished output for that part arrives once the promise resolves. Sibling branches do not wait on it.
- Two sibling async Server Components each await a 300 ms request. Roughly how long does that cost?About 300 ms, not 600 ms — React does not queue siblings behind one another, so both requests are in flight together. The additive case is nesting: when a child can only start its request after data the parent awaited, the latencies stack, and you fix that by hoisting the request or starting it earlier and passing the promise down.
saying these in an interview costs you the question
- Says async Client Components work fine in React 19
- Puts an isLoading state inside the async Server Component
- Claims one await blocks the entire page render
- Thinks sibling awaits always create a waterfall
- Creates the awaited promise fresh inside a client render