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?
answer
- one screen, two values
- the render paused partway through
- only data living outside React
- both halves already committed together
- React must be told about the read
basics
~20 sTearing 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.
solid answer
~50 sTearing is when one committed screen displays two different values that came from the same source. It only became possible once rendering became interruptible: React renders a chunk of components, yields to the browser so it can handle input or a timer, then resumes where it left off. If a mutable value that lives outside React — a module-level store object, a browser API, a cache — changes during that gap, the components rendered before the yield hold the old value and the ones rendered after hold the new one, and React commits both into the same tree. Nothing is racing at the machine level; the mutation happens in an ordinary task while React is parked. The fix has to make React aware that the read happened, so it can check whether the value moved during the render instead of committing a mixed tree.
code
javascript · 13 linesconst cart = { total: 3 };
const readTotal = () => cart.total;
// React renders <Header/> here
const headerSaw = readTotal();
// React yields; the browser runs a click handler in another task
cart.total = 4;
// React resumes and renders <Badge/> here
const badgeSaw = readTotal();
console.log(headerSaw, badgeSaw); // 3 4 -> both land in one commitgo deeper
Be able to say what the word means: one screen showing two different values for the same thing. Know that it involves data kept outside React, not props or useState.
Explain the mechanism: React splits a render across tasks, yields in between, and a mutable value read during render can move in that gap, so earlier and later components disagree in the same commit.
Show you can spot the pattern in a codebase — components reaching directly into a shared mutable object during render — and explain why the bug appears only under load or on slow devices, when React actually yields mid-render.
Frame it as a correctness constraint that adopting concurrent features imposes on an app's whole state layer, and be ready to argue what you audit, what you migrate first, and what inconsistency you are willing to tolerate.
## The name, and what it describes The term is borrowed from graphics, where *screen tearing* means the display shows the top half of one frame and the bottom half of the next. React's version is the same idea applied to a component tree: after a render is committed, one part of the screen shows a value as it was at moment A and another part shows the same underlying value as it was at moment B. Both parts are internally correct. The screen as a whole is a frame that never existed. A concrete shape: a header renders `Items: 3` while a cart badge two hundred pixels away renders `4`, and both derived that number from the same object. ## Why it could not happen in a synchronous render Before concurrent rendering, a React render ran to completion inside a single task. React started at the root, walked the whole tree, and committed — and because JavaScript on a page runs one task at a time, no click handler, timer callback, or network callback could execute in the middle of that walk. Every component read the outside world at effectively the same instant. There was no window in which a value could move. ## The window concurrent rendering opens Concurrent rendering breaks that single walk into slices. React renders some components, checks whether it has spent enough time, and yields control back to the browser so it can paint, handle input, or run a pending callback. Later, React resumes rendering the rest of the tree — continuing the *same* render pass, keeping everything it already computed. That is where the gap lives: ``` render <Header/> reads cart.total -> 3 | | React yields; the browser runs a click handler: cart.total = 4 v render <Badge/> reads cart.total -> 4 commit header "3" and badge "4" land on screen together ``` No parallelism is involved. The mutation and the render slices are separate tasks on one thread; the render is simply spread across several of them. ## Why only data that lives outside React A value held in React state is not read out of a mutable cell during render — it is derived by the render pass from what React has queued for that pass. Every component in one committed tree therefore sees the same version of it. Data that lives outside React has no such versioning. Reading `store.value`, `someCache.get(id)`, or a live browser property during render returns whatever is true at that instant, and two instants are two answers. What counts as "outside React" in practice: - a module-level object or class instance shared across the tree; - a mutable cache or registry a component consults during render; - a live browser value read during render, such as `window.innerWidth` or `navigator.onLine`; - any third-party object that exposes a current value and can change it at any time. ## Tearing is not staleness, and not flicker These get conflated constantly, and interviewers listen for the distinction: - **Stale** means every component shows the same value, and that value is behind reality. It is consistent, just late. Users usually cannot tell. - **Flicker** means the screen changed twice in quick succession — a fallback appearing, a value updating. Every frame was self-consistent. - **Torn** means one frame is internally contradictory. Two elements disagree at the same instant about the same fact, and no amount of waiting resolves what the user already saw. That is why tearing is treated as a correctness bug rather than a polish problem: totals that do not add up, a list showing one set of rows while its counter reports another. ## What a real fix has to accomplish Any fix must give React visibility into the read. React cannot verify a value it never knew was consulted — from the reconciler's point of view, a component that reads a module variable is just a function that returned some elements. Once React knows a component's output depends on an external value, it can record what that value was during the render and check, before putting the tree on screen, whether it still holds. If it moved, the render is thrown away and redone rather than committed. That is the entire purpose of a tearing-safe read path. Moving the value into React state or passing it down as a prop from a single owner also works, for the same reason: it stops being an out-of-band read. ## Why the whole ecosystem cared Before interruptible rendering, letting components reach out and read a shared mutable object during render was a perfectly safe, and very common, way to wire up shared state — it was the cheapest possible subscription. Concurrent rendering retroactively made that pattern unsound, which is why the bindings that connect external state containers to React were rewritten rather than merely tuned. The pattern did not get slower; it stopped being correct.
- JavaScript is single-threaded — so how can anything change while React is rendering?Because React is not rendering the whole time. It renders a slice, yields control back to the browser, and resumes later. The mutation runs in a different task during that gap — a click handler, a timer, a socket message. Nothing executes in parallel; the render is just spread across several tasks, and the world can move between them.
- Could this happen in React 17's legacy synchronous rendering?No. A legacy render walked the whole tree inside one task, so no other code could run between two components and there was no window for the value to change. The inconsistency window opens only once a render can be split across tasks, which is why the problem arrived with concurrent rendering in React 18.
- Is a component showing a stale value the same bug?No. Stale means everything on screen agrees but lags reality — annoying, rarely wrong. Torn means two elements contradict each other at the same instant. Staleness resolves itself on the next render; a torn frame was already shown to the user as a self-contradictory picture.
- Does this only affect state containers, or can plain browser values tear too?Anything mutable that components read directly during render can tear, including live browser properties like window.innerWidth or navigator.onLine. If two components read it at two different moments in one render pass, they can disagree. The source being a store is incidental; the pattern of reading live during render is the problem.
It is screen tearing in a video game: the monitor draws the top of the frame, the game updates the world, the monitor draws the bottom, and you see a picture that stitches two different moments together.
saying these in an interview costs you the question
- Tearing means the screen flickers or flashes while loading
- It is a data race between two threads writing the same variable
- useState values tear too if you update them fast enough
- Wrapping the update in startTransition prevents tearing
- React always renders the whole tree in one pass, so this cannot happen