cswork / libraryOpen practice workspace

Articles

React Concurrent Rendering Interview Questions: Transitions, Interruptions, and Side Effects

Practice React concurrent-rendering questions with urgent input, useTransition, interrupted renders, committed effects, async boundaries, and stale results.

A search input feels instant until a large results list renders on every keystroke. Someone suggests “turn on concurrent rendering.” A strong React interview answer is more concrete: keep urgent input updates responsive, mark the expensive result update as a transition where appropriate, and ensure rendering remains pure when work is restarted or abandoned.

The behavior here follows the current React useTransition reference, render-and-commit explanation, and purity rules. React behavior can change by version, so the current docs are the authority for the examples below. The UI traces are original interview exercises, not candidate reports or claims about a named employer. For adjacent practice, read the React stale closure guide.

Question 1: What does “concurrent” mean here?

It describes React's ability to prepare UI updates in a way that can be interrupted and resumed or restarted before committing. It does not mean ordinary JavaScript component code runs in parallel on another CPU core. The browser's main thread still has work to do, and a long synchronous calculation inside a render can still cause trouble. The useful distinction is between scheduling and committing updates, not a promise of parallel execution.

React's render phase calculates what the next UI should look like. The commit phase applies the completed result to the DOM. Rendering can be repeated or abandoned before commit, especially with interruptible work; a committed UI is the visible result. Because render is not guaranteed to happen exactly once for each user-visible update, side effects in render are unsafe. That is why an interviewer may ask about analytics events, network calls, or mutations placed directly in a component body.

A transition lets React treat some state updates as non-urgent. If a newer, urgent update arrives, work associated with a transition can be interrupted and started again. That helps responsiveness when updating a results panel is expensive. It does not automatically make every operation faster or fix a slow API. Identify which work blocks the interaction before choosing a React API.

Question 2: Which state belongs in a transition?

Imagine a controlled search box with query for the characters displayed in the input and filter for a costly results view. The input's controlled state should update urgently so typing stays responsive. A transition can schedule a separate result update:

const [query, setQuery] = useState('');
const [filter, setFilter] = useState('');
const [isPending, startTransition] = useTransition();

function handleChange(event) {
  const next = event.target.value;
  setQuery(next);
  startTransition(() => setFilter(next));
}

The current React documentation states that transition updates cannot control text inputs. Marking the only query update as a transition would make the controlled input behave poorly or violate the intended urgent path. Splitting the displayed input from the expensive view makes the prioritization explicit. isPending can show that the transition has not yet completed, but a pending indicator should not erase already useful results unless the product deliberately chooses that behavior.

This snippet is an illustration, not a universal implementation. If the expensive work is a network request, the API response and race handling need separate thought. If the list calculation is small, a transition may add complexity without a user-visible gain. Profile the interaction, observe typing latency and result latency, and determine whether rendering, network, parsing, or a child component is the bottleneck.

An interruption trace to explain at the whiteboard

The table is an original practice trace. It assumes a large result tree whose transition render takes long enough for another keystroke to arrive. Actual scheduling details depend on workload and React version; the table describes the decision boundary, not a guaranteed millisecond-by-millisecond timeline.

MomentUrgent input pathTransitioned results pathSafe interpretation
User types rInput state becomes rResults for r start preparingTyping need not wait for the whole result tree
Before commit, user types reInput state becomes reOlder results work may be interruptedRender for r may have run without appearing
Results for re completeInput remains reLatest completed UI commitsCommit is the visible boundary
Analytics call was made during render of rNo visible r result may existA side effect already escapedRender is the wrong place for that call

The last row is the key interview insight. A render-time mutation or network request can happen for work React later discards. Even without a transition, development Strict Mode can call render logic more than once to reveal impurities. Code should not rely on render being a one-time event. Move external synchronization to an appropriate Effect, event handler, or data-fetching abstraction, and design it so setup and cleanup are correct. Do not use an Effect merely to compute a value already derivable during render; purity is about where side effects belong, not moving all work out of render.

Question 3: Where should side effects go?

An event handler is appropriate for a side effect caused by a specific user action, such as submitting a form. An Effect is appropriate when a component needs to synchronize with an external system because it is on screen, for example subscribing to a service and cleaning up that subscription. The React purity rule says components and Hooks should be idempotent and have no side effects in render; effects run separately from rendering.

Suppose a component body calls analytics.track('results-shown'). A transition render may execute and then be interrupted, so the event can describe UI the user never saw. If the requirement is “track a committed result becoming visible,” place the synchronization on the committed state, with idempotency or deduplication appropriate to the analytics contract. If the requirement is “track a click,” put it in the click handler. Those are different product events; neither should be inferred from a render attempt.

Similarly, do not mutate a module-level cache, append to an array, or increment a counter while calculating JSX. A render restart would produce a different answer from the same inputs. Local temporary values created during render are fine when they are derived without observable external mutation. The test is whether running the render calculation again with the same props and state would have an unintended external effect.

Question 4: What happens around await in a transition?

The current useTransition reference documents an important boundary: React marks state updates made synchronously within the function passed to startTransition. For state updates after an await, the docs currently require another startTransition to mark those later updates as transitions. This is a version-sensitive detail; check the React version in the interview scenario rather than assuming an older blog's behavior.

For example, starting an async action can express pending work, but the update after the awaited request needs deliberate classification. If it controls the input, it should remain urgent; if it updates the results panel, another transition may be appropriate. A candidate who simply wraps an entire async function and says “all later state is low priority” misses this boundary. The React reference explicitly calls out this limitation.

Also separate scheduling from request ordering. If searches for r and re issue network calls, the r response can arrive after re. A transition does not by itself make stale responses safe. Use a request identity, abort handling where supported, or a framework/data layer that ensures only the relevant response becomes current. Then test the sequence with deliberately reversed response times. Our React stale closure guide examines that adjacent race.

Question 5: How do you debug a UI that still freezes?

First profile the actual interaction. Measure input-to-paint latency, result-commit latency, and the duration of expensive render work. Check whether a long synchronous filter or parse monopolizes the main thread even before React can yield. A transition changes update priority; it cannot preempt arbitrary blocking JavaScript inside one long function. Chunk, memoize, virtualize, or move suitable computation when the profile supports that decision.

Second, check state placement. If every keystroke updates a large shared parent, unrelated children may rerender urgently. Moving state closer to the input can reduce work; transitioning the result state can preserve responsiveness. If the expensive tree is already small, optimize only after measuring. useDeferredValue is another documented option when a value should lag behind urgent input, especially when you cannot directly control the update source. It is not a substitute for fixing a render-time side effect.

Third, test the user-visible contract. While isPending is true, does the screen keep prior results, show progress, and avoid suggesting that stale results match the latest query? Does keyboard navigation remain predictable? Does an abandoned render cause duplicated analytics or mutations? “It no longer freezes” is not enough if the UI displays misleading data or sends repeated actions.

Finally, reproduce under a production build and representative data, then use development checks for purity. Development Strict Mode can deliberately rerun code; production scheduling and timings differ. Do not dismiss a duplicate event solely because it appeared under Strict Mode: the duplicate often reveals an effect or render that is not safe to repeat. Fix the underlying assumption and confirm production behavior.

One useful test records input paint, results commit, and network response as three separate timestamps. That prevents a faster input from hiding a results panel that now lags too long. The product may need a pending label, cancellation behavior, or a smaller result set. The scheduling API is only one part of the user experience.

A concise interview answer

“I would keep controlled input updates urgent and mark the expensive results update as a transition. That allows React to interrupt stale results work, but it does not run my component code on another thread or solve network races. I would profile the main-thread work, keep render pure, and place user-action side effects in handlers and external synchronization in Effects with cleanup. I would test a second keystroke arriving before the first results commit and verify that no abandoned render performs an observable action. For async transition actions, I would check the current React rule around state updates after await.”

Practice questionConnection to this scenario
Debug and optimize React performance issuesProfile slow renders before choosing a scheduling tool.
Implement debounced async autocomplete inputSeparate input urgency from async results.
Build a Reliable Streaming Chat UIPreserve responsiveness under incremental updates.
Implement and Improve React ComponentsExplain state placement and component boundaries.
Build a Race-Free Memory Card Game in ReactReason about events, state, and stale work.

Continue with the React stale closure interview guide. Use a deliberately delayed search response to test both concerns: interrupted rendering must not leak side effects, and an older network result must not replace the newer query.

Sources and further reading