๐Ÿณ IntuitiveFE
Login
โ† All concepts

Concurrent Features: Transitions & Suspense

โฑ๏ธ ~4-minute bite ยท solve the sandbox to master

0%lesson
๐Ÿง’

5-Year-Old Metaphor

โ€” The physical, real-world picture. No jargon.

๐ŸŽ›๏ธ Concurrent React = a DJ with two decks. Can mix the next track (new render) without interrupting the current one. startTransition = "play this next, but not urgently."

Synchronous React (before Concurrent)

One render at a time. React starts rendering โ†’ must finish before any other work. Like a single-deck DJ: must finish the current track before starting the next. User input waits in line.

Concurrent React (React 18+)

Multiple renders can be in-flight at different priorities. High-priority (user input) interrupts low-priority (list update). React can pause, resume, or abandon in-progress renders.

The priority system

UrgentUser input (typing, clicking)
TransitionstartTransition updates, page navigations
DeferreduseDeferredValue, background computations
๐ŸŽ›๏ธ

Interactive Sandbox

โ€” Move something, see it react instantly.

Concurrent Feature

Scenario

Filtering 10,000 items while the user types in a search box

Every keystroke triggers a synchronous re-render of 10,000 items. The browser can't process new keystrokes until the render completes โ†’ input lags visibly at 200ms+ per render.

startTransition โ€” implementation
js
1function Search() {
2 const [query, setQuery] = useState('');
3 const [results, setResults] = useState(allItems);
4 const [isPending, startTransition] = useTransition();
5ย 
6 function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
7 // Input update: synchronous (high priority)
8 setQuery(e.target.value);
9ย 
10 // List update: transition (can be interrupted)
11 startTransition(() => {
12 setResults(allItems.filter((item) =>
13 item.name.includes(e.target.value)
14 ));
15 });
16 }
17ย 
18 return (
19 <>
20 <input value={query} onChange={handleChange} />
21 {isPending && <Spinner />}
22 <ItemList items={results} />
23 </>
24 );
25}
Gotcha: Do NOT wrap controlled input's onChange in startTransition โ€” text input must update synchronously or the input feels broken. Only wrap the secondary expensive update.
Insight: startTransition is not setTimeout. setTimeout delays work by a fixed amount. startTransition yields to the React scheduler, which resumes the transition when the browser is idle. It adapts to device speed automatically.
Explored:๐Ÿšฆโณโธ๏ธ๐Ÿ’ค๐Ÿ“ก
๐ŸŽฏ

Challenge

Compare the 'without' and 'with concurrent' descriptions for all 5 patterns.

Try it
๐ŸŽฏ

Why Should I Care?

โ€” The exact interview question + the bug it kills.

Interview questions

Q: What is the difference between startTransition and setTimeout for deferring work?

setTimeout delays work by a fixed duration regardless of what the browser is doing. startTransition defers work to when the React scheduler has capacity โ€” it's priority-based, not time-based. React can interrupt a transition render if higher-priority work (user input) arrives. setTimeout cannot be interrupted once the timeout fires.

Q: How does Suspense know when to show the fallback?

Suspense uses a protocol where components "throw" a Promise during render. When React encounters a thrown Promise, it shows the nearest Suspense fallback. When the Promise resolves, React retries the render. Libraries like TanStack Query, React.lazy, and React 19's use() all implement this protocol.

Q: What is "tearing" and why does Concurrent Mode need useSyncExternalStore?

js
1// Tearing: different parts of the UI show different
2// values from the same external store during one render
3ย 
4// Concurrent React can pause mid-render.
5// If an external store updates during the pause:
6// โ†’ some components render with old value
7// โ†’ some render with new value
8// โ†’ UI is torn (inconsistent state visible to user)
9ย 
10// Solution: useSyncExternalStore ensures atomic snapshots
11const count = useSyncExternalStore(
12 store.subscribe, // subscribe to changes
13 store.getSnapshot, // get consistent snapshot
14);
15// Used internally by Redux, Zustand, Jotai
๐Ÿ”ฌ

The Deep Dive

โ€” Spec refs, engine internals, the minutiae.

React scheduler internals

React's scheduler uses MessageChannel to yield control back to the browser between render chunks. MessageChannel callbacks run between paint frames โ€” higher priority than setTimeout (which has a minimum 4ms delay) but lower priority than microtasks. This allows the browser to process input events between chunks of React rendering work.

Priority lanes

React uses a bitmask "lanes" system to track priority. Each update is assigned one or more lanes (SyncLane, TransitionLane, IdleLane, etc.). The scheduler picks the highest-priority non-empty lane and works on it. When a high-priority lane arrives, the current low-priority render is interrupted and the high-priority update is processed first. The interrupted work is resumed when no higher-priority lanes are pending.

Suspense protocol โ€” promise throwing

js
1// Simplified Suspense protocol (how libraries implement it)
2function useSuspenseData(key) {
3 const cache = getCache();
4 const entry = cache.get(key);
5ย 
6 if (!entry) {
7 // Throw a Promise โ€” React catches it, shows fallback
8 const promise = fetchData(key).then((data) => {
9 cache.set(key, { status: 'resolved', data });
10 });
11 cache.set(key, { status: 'pending', promise });
12 throw promise; // โ† Suspense protocol
13 }
14ย 
15 if (entry.status === 'pending') throw entry.promise;
16 if (entry.status === 'error') throw entry.error;
17 return entry.data; // resolved โ€” return the data
18}
๐ŸŽค

Interview Questions

โ€” Real questions from real interviews โ€” with answers.

startTransition is priority-based and interruptible; setTimeout delays by a fixed time and is not interruptible.

A component 'throws' a Promise during render; React catches it and shows the nearest Suspense fallback until the Promise resolves.

Tearing: different components read different snapshots of an external store in one render, causing UI inconsistency.

useTransition when you own the setState call; useDeferredValue when you only receive a prop/value.

Text input value must update synchronously or the input becomes unresponsive and shows the wrong character.

React uses a bitmask 'lanes' system; high-priority lanes (SyncLane) interrupt and preempt low-priority ones (TransitionLane).

๐ŸŽฎ

Memory Game

โ€” Quick quiz โ€” lock the concept in long-term memory.
1/4

What is React's scheduler responsible for?