What does useDeferredValue() do
What useDeferredValue() does
useDeferredValue(value) returns a "deferred version" of a value.
If value changes often (for example, while typing in an input), React may not immediately propagate the new value into heavy parts of the UI (lists, charts), so the interface stays responsive. The input reacts instantly, while the "expensive" render catches up a little later.
A mini example:
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query); // a "slow" copy
// The heavy list filters by deferredQuery, not by the instant query
const filtered = useMemo(() => heavyFilter(items, deferredQuery), [items, deferredQuery]);While the user types, query changes right away (the input does not lag), while deferredQuery updates a little later - the heavy render does not "jerk" on every character.
How useDeferredValue() is similar to debounce
Both techniques smooth out frequent changes, so heavy work does not get triggered too often.
The similarity:
- They lower the frequency of "expensive" recalculations/renders on fast input.
- The user sees a smooth UI with no lag.
How useDeferredValue() differs from debounce
The main differences are in level and semantics:
| Criterion | useDeferredValue() | Debounce (for example, debounce(fn, 300)) |
|---|---|---|
| Where it works | Inside React, understands priorities, integrated with Concurrent Rendering | Outside React (a timing utility), "blindly" delays calls |
| What it defers | Propagating a value to consumers; the render can be interrupted/resumed | Calling a function on a timer after a pause |
| Delay control | Dynamic, no hard timer; React decides itself when to "catch up" | A fixed interval (for example, 300 ms) |
| Cancellation/interruption | Yes: the render can be interrupted if a more important update arrives | Yes: the timer resets on new input, but with no knowledge of React's priorities |
| SSR/hydration/concurrency | Naturally compatible | Not integrated at all |
| Typical use cases | Decoupling "fast" input from "heavy" UI | Network/handlers, throttling call frequency, autocomplete APIs |
In short: debounce is about time, useDeferredValue is about render priority.
How useDeferredValue() differs from startTransition()
Both mechanisms serve "non-urgent" updates, but they apply at different points:
| Criterion | useDeferredValue(value) | startTransition(() => setState(...)) |
|---|---|---|
| Level | A value: gives back a "slow" copy of a frequently changing value | An operation/update: marks specific setState calls as low priority |
| Who initiates it | The consumer of the value chooses the "slow" version | You yourself mark a piece of updates as a transition |
| Status control | No built-in isPending | You get isPending via useTransition() |
| Granularity | Passive: any place that uses the deferred value will "catch up" later | Active: you explicitly wrap "heavy" updates in a transition |
| Typical use cases | Heavy memoized computations/lists that depend on a fast-changing value | Navigation, filtering, requests, large-scale tree rebuilds |
| Asynchrony (React 19+) | Not about async | startTransition can be await-ed (waiting for async work) |
In short:
useDeferredValue- "give me a version of this value that can update later".startTransition- "make these state updates low priority".
When to choose which
- A heavy list/chart reacts to fast input → start with
useDeferredValue(query). Simple and almost no code. - You need to mark a batch of updates as "non-urgent" and show an "updating" status →
const [isPending, startTransition] = useTransition(); startTransition(() => setState(...)). - Limit the frequency of network calls / input handlers → debounce (network layer/utilities). You can combine them: debounce for the request,
useDeferredValuefor the heavy UI.
Common patterns
Combo "input + deferred filtering":
const [query, setQuery] = useState('');
const deferred = useDeferredValue(query);
const list = useMemo(() => heavyFilter(items, deferred), [items, deferred]);Mark a rebuild as a transition (with an indicator):
const [isPending, startTransition] = useTransition();
const onTabChange = (tab: string) => {
startTransition(() => setActiveTab(tab)); // a large rebuild of the children
};
return <>
<Tabs onChange={onTabChange} />
{isPending && <Skeleton />}
<HeavyPane tab={activeTab} />
</>;Debounce only for the request:
const debouncedFetch = useMemo(() => debounce(fetchUsers, 300), []);
useEffect(() => {
debouncedFetch(query);
}, [query, debouncedFetch]);(and in parallel you can use useDeferredValue(query) for a "smooth" UI)
Summary
useDeferredValue- a deferred version of a value. About priority, not a timer.- Debounce - a timer: it defers function calls after a pause.
startTransition- marking state updates as low priority, withisPendingand (in React 19+)await.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.