skip to content

Concurrent Rendering

Concurrent rendering lets React start, pause, resume, or abandon a render so urgent work such as typing never waits behind an expensive one. Interviewers use this area to separate people who have merely called startTransition from people who understand what it changes in the pipeline.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

14

In a React 19 search box, the onChange handler calls setQuery(value) directly but wraps setFilterTerm(value) in startTransition. Why must the input's own state update stay outside the transition?

level: juniorimportance: must knowfreq 55%

answer

  1. two updates from one keystroke
  2. the field must never lag
  3. transitions are interruptible, inputs are not
  4. only the expensive half is non-urgent

basics

~20 s

The typed value is urgent: React must commit it right away or the characters the user typed appear late. An update marked with startTransition is non-urgent and interruptible, so putting the input's own value in one makes the field feel stuck.

solid answer

~50 s

One keystroke produces two logically different updates. The input's value must be committed immediately, because a controlled input only shows the new character after React commits the state holding it — React's own guidance is that updates to controlled inputs cannot be transitions. The expensive consequence of typing — filtering a large list — is what you mark with `startTransition`, which tells React the update is non-urgent: it may render it in the background, pause part-way, and throw the half-finished render away when the next keystroke arrives. So the split is deliberate: `setQuery` stays urgent so typing stays instant, `setFilterTerm` becomes a transition so the slow list render can be interrupted rather than blocking the keyboard. Marking the input's value as a transition inverts that and produces exactly the lag you were trying to remove.

go deeper

for a junior

Know that one keystroke produces two updates: the field's value, which must be immediate, and the expensive consequence, which can wait. Say plainly that a controlled input's value never belongs inside startTransition.

for a middle

Explain what transition priority actually changes — the render becomes interruptible and discardable, not delayed — and why an interruptible commit makes a controlled field display stale text.

for a senior

Be ready to judge when the split is worth its cost: two sources of truth and a visible lag between field and results are only justified when the downstream render is measurably expensive.

for a principal

Frame it as a responsiveness budget: which updates in a product must be frame-accurate, which may lag, and how the team encodes that policy so priority marking is consistent instead of sprinkled where someone noticed jank.

## What "urgent" means to React React 19 sorts every state update into one of two broad priorities. An update produced directly by a user interaction — typing, clicking, dragging — is **urgent**: React renders and commits it before doing anything else, because the user is watching for the result of their own keystroke. An update scheduled inside `startTransition` is **non-urgent**: React may render it in the background, stop part-way through, and discard the unfinished work if a more important update arrives. The important part is what a transition is *not*. It does not delay the update by a timer, and it does not skip it. The state change is still requested at the moment you call the setter. What changes is that this render loses every race against urgent work. ## One event, two updates A filtered search box produces two conceptually different updates from a single keystroke: ```jsx function Search({ items }) { const [query, setQuery] = useState(''); const [filterTerm, setFilterTerm] = useState(''); function handleChange(e) { setQuery(e.target.value); // urgent: what the field shows startTransition(() => setFilterTerm(e.target.value)); // non-urgent: the slow list } return ( <> <input value={query} onChange={handleChange} /> <BigFilteredList items={items} term={filterTerm} /> </> ); } ``` `query` exists purely so the text field shows what was typed. `filterTerm` exists to drive a potentially thousands-of-rows render. They travel at different speeds on purpose. ## Why the input's value cannot be non-urgent A controlled input displays whatever its `value` prop says. The browser paints the typed character optimistically, but React overwrites the field's value with state on the next commit. If that commit is interruptible, React is free to postpone it while it works on something else, and the field can display text that is a keystroke or two behind the keyboard — the caret jumps, fast typists lose characters visually, and the component feels broken. React's documented rule is direct: do not use a transition for a controlled input's value, because those updates must be applied synchronously. This is also why you cannot "just wrap the whole handler" in `startTransition` and call it done. Everything scheduled inside that callback inherits transition priority, including the input's value. ## What the split actually buys With the split in place, the sequence for one keystroke is: React commits the urgent `query` update immediately, so the character appears; then it starts rendering the list with the new `filterTerm` in the background. If the user types again before that background render finishes, React abandons it and starts over with the newest term. The list never blocks the keyboard, and the user never sees a half-built list — React only commits a transition render once it is complete. While the transition render is outstanding, the previous list stays on screen and stays interactive. That stale-but-usable interval is the point, and `useTransition`'s pending flag exists so you can dim or annotate it. ## Two updates, or one plus a deferred value Holding two pieces of state is not the only shape. If a single `query` state drives both the field and the list, you can keep one urgent state and lag the value the expensive subtree reads instead: ```jsx const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); // <input value={query} …/> renders urgently; <BigFilteredList term={deferredQuery} /> lags behind ``` The division of labour is identical: the field reads the urgent value, the expensive subtree reads the one that is allowed to lag. Choose the two-state form when you already have two setters; choose the deferred form when the expensive component receives a value you do not own the setter for. ## The failure you are avoiding Without any priority split, one keystroke renders the input *and* the huge list synchronously. Because React's render and commit run on the main thread, the browser cannot paint the typed character until that whole render finishes. On a big list that is tens or hundreds of milliseconds per key, which reads as a laggy, sticky text field. Marking only the expensive half as a transition removes the coupling — not by making the list render faster, but by letting the fast update overtake and interrupt the slow one.

  • What happens if you wrap the entire onChange handler body in startTransition instead of just the filter update?
    Every setter called inside that callback inherits transition priority, including the input's own value. The field's displayed text then becomes interruptible and can lag behind the keyboard, which is the exact symptom the split was meant to remove. Only the expensive downstream update belongs in the callback.
  • Does putting an update in a transition mean React might never apply it?
    No. A transition update always lands eventually; React only discards renders that are already stale because a newer update superseded them. When the value changes three times in quick succession, React may abandon two in-progress renders, but the final state is the newest value, committed once.
  • Is there a benefit to the split when the list is small and renders in two milliseconds?
    Essentially none, and it adds a second source of truth plus a possible visible lag between the field and the results. Priority splitting pays off only when the downstream render is expensive enough to be felt; otherwise keep one piece of state and render normally.

saying these in an interview costs you the question

  • Says startTransition delays the update by a timeout
  • Wraps the whole handler, including the input's value
  • Thinks a transition update can be dropped entirely
  • Claims transitions move the render off the main thread
  • Believes the list re-renders faster inside a transition

context

open as a page

In React 19's concurrent rendering, what is tearing, and how can a single committed screen end up showing two different values that both came from the same shared source?

level: middleimportance: must knowfreq 42%

basics

~20 s

Tearing is a UI inconsistency where one committed React tree shows two different values read from the same external source, because React paused mid-render, the source changed during the pause, and later components read the newer value.

open as a page

In a React 19 app, a large list is mid-render inside a transition when the user types another character into the search input. React shows the new character immediately and updates the list afterwards. What does React do internally so that one pending update jumps ahead of another?

level: middleimportance: must knowfreq 58%

basics

~20 s

React stamps each update with a priority lane when the setter is called: a keystroke gets an urgent lane, a transition a low one. The scheduler renders the highest pending priority first, discards the unfinished transition render, and restarts it afterwards.

open as a page

A teammate replaces a 300 ms debounce on an expensive filtered list with React 19's startTransition and says the two are equivalent. How do they actually differ in what work runs and what the user sees?

level: middleimportance: must knowfreq 68%

basics

~20 s

A debounce postpones starting the work until the user pauses; a transition starts it immediately but at a priority React can interrupt and discard. So a transition shows fresh results as fast as the machine allows and never inserts an artificial wait, while a debounce is a fixed guess at how long to stall.

open as a page

In React 19, does "concurrent rendering" mean React renders components in parallel on several threads or inside a Web Worker? Explain what actually happens on the main thread.

level: juniorimportance: should knowfreq 42%

basics

~20 s

No. Concurrent rendering is interleaving, not parallelism. React still renders on the one main thread; it can split a render into slices, yield to the browser between them, and abandon an in-progress render to do urgent work first.

open as a page

Why can a value held in React state with useState or useReducer never tear during a concurrent render, while a value read from a mutable module-level object can?

level: middleimportance: should knowfreq 34%

basics

~20 s

React state is versioned, not live: one render pass derives state from each fiber's update queue, so every component it commits sees the same version. A mutable module object is read live, so two reads during one render can differ.

open as a page

During a concurrent render, React's scheduler stops partway through, hands control back to the browser, and continues the render in a later task. What decides when it yields, how does it get control back, and what unit of work can it never break up?

level: middleimportance: should knowfreq 38%

basics

~20 s

React checks between units of work — roughly one component each — whether the current slice has used its small time budget. If so it stops and posts a browser task to continue later. It can never interrupt the inside of one component function.

open as a page

A React 19 component does `const deferredQuery = useDeferredValue(query)` and renders a large list from deferredQuery. Walk through the renders React performs when query changes from 'ab' to 'abc'.

level: middleimportance: should knowfreq 52%

basics

~20 s

React renders twice. First an urgent render in which the hook still returns the old value, so the fast parts of the UI update while the big list stays as it was; then a background, interruptible render in which the hook returns the new value and the list is rebuilt.

open as a page

A custom hook subscribes to an external store inside useEffect and mirrors the current value into useState on every change. Under React 19 concurrent rendering, why can components using that hook still show inconsistent values in one commit?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Because an effect cannot run in the middle of a render. A component mounting during a render seeds its copy from the store as it is at that moment, while already-mounted components still hold whatever their subscription last delivered — so one commit contains two versions.

open as a page

When a component reads an external store through useSyncExternalStore, how does React detect that the store changed partway through a render, and what does it do so the committed tree stays consistent?

level: seniorimportance: should knowfreq 31%

basics

~20 s

React remembers the snapshot each component rendered with, then re-reads it before committing and compares with Object.is. If any value moved, React throws that render away and re-renders synchronously — which only works if the snapshot value is stable between calls.

open as a page

A team wrapped almost every setState in their React 19 app in startTransition to "keep the UI responsive". Under fast typing, results now lag far longer than before and the app burns more CPU. What is the mechanism, and how would you fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Priority is relative, so marking everything low-priority prioritises nothing. Each urgent keystroke preempts the in-flight low-priority render, which restarts from scratch, repeating work and delaying the commit until typing pauses. Mark only the expensive downstream update.

open as a page

You wrapped an expensive list update in React 19's startTransition, but typing in the search field is still visibly janky. What can make a transition fail to restore responsiveness?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Transitions change priority, not cost. React can only yield between units of work, so one component with a very slow render still blocks a frame; and updates can escape the transition — scheduled outside its callback, or forced synchronous — leaving the whole render urgent again.

open as a page

React tracks the work pending on a root as "lanes" — bits in a bitmask — rather than as a single current priority number. What does that representation let React do that one number could not?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

A bitmask records a whole set of pending priorities at once, so React can render a chosen subset in one pass, keep the rest marked as still pending, and tell otherwise-similar updates apart — all with cheap bitwise set operations.

open as a page

Your team wants to adopt React 19 transitions across a large app whose shared state lives in hand-written module-level stores that components read directly during render. How do you reason about the tearing risk that introduces, and what do you do about it?

level: principalimportance: nice to knowfreq 19%

basics

~20 s

Rank each shared mutable source by whether it is read during render, how often it changes, and whether a visible disagreement would matter. Route the ones that qualify through a single tearing-safe read path, accept the rest deliberately, and budget for the interruptibility those reads give up.

open as a page