React’s concurrent features enable applications to remain responsive during expensive rendering work by allowing React to interrupt, pause, and prioritize updates.
Time slicing and work interruption
Time slicing is the technique that allows React to break rendering work into small chunks and spread them across multiple frames, keeping the UI responsive.
Time slicing is the foundation of React’s concurrent mode. Without it, long renders would block the main thread and make the UI feel frozen.
How time slicing works
Start rendering
React begins processing a render with a specific priority level assigned to lanes.
Process units of work
React processes Fiber nodes one at a time, performing reconciliation work.
Check deadline
After each unit of work, React checks if it has exceeded its time budget (typically 5ms).
Yield or continue
If the deadline is reached, React yields control to the browser. Otherwise, it continues with the next unit of work.
Resume later
The browser handles high-priority tasks (like input events), then React resumes rendering from where it left off.
Without time slicing
With time slicing
In traditional React, a large render blocks the main thread until completion:
- User input feels sluggish
- Animations stutter
- The page appears frozen
- All updates are synchronous
Concurrent React breaks work into chunks:
- User input remains responsive
- Animations stay smooth at 60fps
- UI updates are interruptible
- High-priority work can interrupt low-priority work
Work interruption
When a higher-priority update arrives during a render, React can interrupt the current work and start processing the urgent update.
Detect high-priority update
A user interaction (like a click or text input) triggers a high-priority state update.
Interrupt current render
React immediately stops the current low-priority render, even if it’s halfway through.
Process urgent update
React processes the high-priority update in a new render pass.
Discard or resume
After the urgent update commits, React either discards the interrupted work (if it’s now stale) or resumes it.
Work interruption is why the render phase must be pure and side-effect free. React may call render functions multiple times or discard renders that never commit.
Priority inversion prevention
React prevents priority inversion where low-priority work blocks high-priority work:
Lane entanglement
Starvation prevention
Batching
When a low-priority update reads state from a high-priority update, their lanes become entangled. This ensures they render together and prevents inconsistent UI states.
Low-priority updates that wait too long are automatically promoted to higher priority lanes, ensuring they eventually complete.
Multiple updates in the same priority lane are batched together, reducing unnecessary renders.
Suspense and lazy loading
Suspense allows components to “wait” for asynchronous operations while showing a fallback UI. It’s React’s declarative approach to loading states.
Basic Suspense usage
Start rendering
React begins rendering the component tree inside the Suspense boundary.
Component suspends
When a component isn’t ready (e.g., code hasn’t loaded), it throws a Promise.
Catch the Promise
The nearest Suspense boundary catches the thrown Promise and shows the fallback UI.
Wait for resolution
React waits for the Promise to resolve (e.g., code chunk loads).
Retry render
Once the Promise resolves, React retries rendering the component, which now succeeds.
Suspense works by catching thrown Promises during render. This is why data fetching libraries for Suspense must throw Promises when data isn’t ready.
Code splitting with lazy
The lazy function enables component-level code splitting:
The entire component is bundled in the main JavaScript file, increasing initial load time. The component is split into a separate chunk and loaded on demand when first rendered.
Nested Suspense boundaries
You can nest Suspense boundaries to show loading states at different granularities:
Independent loading states
Each Suspense boundary manages loading state for its children independently.
Granular fallbacks
Show specific loading UI for each section rather than a single page-wide spinner.
Progressive disclosure
Ready parts of the UI appear immediately while slower parts continue loading.
Nested Suspense boundaries enable progressive loading patterns where fast content appears first, with slower content appearing incrementally.
useTransition and useDeferredValue
These hooks provide APIs for marking updates as non-urgent, keeping the UI responsive during expensive state changes.
useTransition
useTransition allows you to mark state updates as transitions, which are lower-priority and can be interrupted.
Urgent updates
Transition updates
Updates outside startTransition are synchronous and high-priority:
- User input in form fields
- Focus changes
- Toggling UI elements
- Immediate visual feedback
Updates inside startTransition are interruptible and lower-priority:
- Search results
- Filter/sort operations
- Route navigation
- Heavy computations
Mark as transition
Wrap state updates in startTransition to mark them as non-urgent.
Keep UI responsive
React keeps the current UI interactive while processing the transition in the background.
Interrupt if needed
If the user makes another change, React can interrupt the transition and start a new one.
Show pending state
The isPending flag lets you show loading indicators during the transition.
Transitions solve the problem of “slow updates blocking fast updates.” The input field updates immediately while expensive filtering happens in the background.
useDeferredValue
useDeferredValue provides a deferred version of a value that lags behind the actual value during transitions.
useTransition
useDeferredValue
Explicitly mark state updates as transitions. Gives you an isPending flag. Create a deferred version of a value. Simpler API when you don’t control the state update.
When to use transitions
Expensive renders
Use transitions when state updates trigger expensive renders that would otherwise make the UI feel frozen.
Navigation
Route changes often involve loading new components and data. Transitions keep the current page interactive during navigation.
Filtering/sorting
When filtering or sorting large lists, transitions allow the filter input to update immediately while results update in the background.
Real-time search
Showing search results as the user types can be slow. Transitions keep typing responsive while results update.
Transitions are most effective when the slow part is rendering, not data fetching. For slow data fetching, combine transitions with Suspense.
Automatic batching
React automatically batches multiple state updates that occur in the same event handler or async context into a single re-render.
Before React 18
React 18+
Automatic batching improves performance by reducing unnecessary re-renders. If you need to opt-out, use ReactDOM.flushSync() to force synchronous updates.