Concurrent rendering in React
What is Concurrent Rendering?
Concurrent rendering (React 18+) allows React to interrupt rendering work to handle more urgent updates. Instead of rendering the entire tree synchronously (blocking), React can work on multiple versions of the UI simultaneously, pausing and resuming as needed.
Synchronous vs Concurrent
Before React 18 (Synchronous)
User types β React renders 10,000 items β UI frozen β Input updates
^^^^^^^^^^^^^^^^^^^^^^^^
Can't be interrupted!React 18+ (Concurrent)
User types β React starts rendering β User types again β React STOPS
React restarts with latest input β Renders β UI updates
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Interruptible! Prioritized!Key Concepts
1. Interruptible Rendering
React can pause work on a low-priority update to handle a high-priority one (like user input), then resume later.
2. Automatic Batching
React 18 automatically batches all state updates, even in async functions:
// React 17 β each setState triggers separate re-render in async
setTimeout(() => {
setCount(c => c + 1); // Re-render 1
setFlag(f => !f); // Re-render 2
}, 1000);
// React 18 β automatically batched into ONE re-render
setTimeout(() => {
setCount(c => c + 1); // Batched
setFlag(f => !f); // Batched β Single re-render
}, 1000);3. Transitions
Mark non-urgent updates with startTransition:
import { startTransition } from "react";
// Urgent: show what user typed
setInputValue(input);
// Non-urgent: filter and display results
startTransition(() => {
setSearchResults(filterItems(input));
});4. Suspense for Data Fetching
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<ContentSkeleton />}>
<MainContent /> {/* Streams when ready */}
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<Comments /> {/* Streams independently */}
</Suspense>
</Suspense>
);
}Concurrent Features Summary
| Feature | What it does | Hook / API |
|---|---|---|
| Automatic Batching | Batches all state updates | Built-in (React 18) |
| Transitions | Marks updates as non-urgent | useTransition, startTransition |
| Deferred Values | Defers a value update | useDeferredValue |
| Streaming SSR | Sends HTML in chunks | Suspense + server |
| Selective Hydration | Hydrates parts independently | Suspense + hydration |
How to Enable Concurrent Rendering
// React 18 β use createRoot (enables concurrent features)
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
// β Legacy β ReactDOM.render (no concurrent features)
// ReactDOM.render(<App />, document.getElementById("root"));Practical Benefits
// Without concurrent rendering:
// Typing in search box is laggy because React blocks on filtering 50,000 items
// With concurrent rendering:
function Search({ items }: { items: Item[] }) {
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
return (
<div>
<input
value={query}
onChange={e => {
setQuery(e.target.value); // Urgent: update input
startTransition(() => { // Non-urgent: filter
setFilteredItems(filterItems(items, e.target.value));
});
}}
/>
{isPending && <Spinner />}
<ItemList items={filteredItems} />
</div>
);
}
// Input stays responsive! Filtering happens in background.Important:
Concurrent rendering is opt-in per feature β your existing code continues to work. Enable it with createRoot, then use useTransition and useDeferredValue to mark non-urgent updates. React automatically handles batching, interruption, and prioritization. The result: apps that feel more responsive, especially with heavy rendering or slow data.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.