You don't need to know React's scheduler internals to use it well. You do need a model of what 'high priority' and 'low priority' actually mean — otherwise the hooks just feel like magic.
Three priority levels (mental model)
- Synchronous / urgent — input events, controlled-input updates, click responses. These render immediately. The user sees the change in the same tick.
- Transition / non-urgent — anything inside
startTransition. React renders these without blocking the main thread; the browser stays responsive. - Suspense / async — anything that suspends. React waits for the promise, keeps showing previous content (with a transition) or the fallback (without).
You don't pick the priority manually — you pick the hook (useState alone, startTransition, use + Suspense) and the priority follows.
Batching
React 19 batches all state updates inside the same event handler, async function, or callback. setA(1); setB(2); in a click handler triggers one render. This is automatic — you don't need flushSync for the common case.
Yielding
During a transition render, React periodically yields to the browser. If the user types or clicks, React processes the urgent event first, then resumes the transition. This is what keeps the app responsive during heavy renders.
What this means for your code
Most of the time: nothing. React handles it. The places it matters:
- Mark heavy state updates with useTransition (lesson 1).
- Use useDeferredValue for props you don't own (lesson 2).
- Trust the React Compiler (next lesson) to memoize wherever it helps.
- Don't reach for
flushSync— almost never the right answer in 2026.
Scheduling is React's job; semantics are yours. You tell React what you mean (this update is urgent, that one isn't); React decides when to actually paint each one. Trying to control timing manually fights the scheduler instead of using it.