Why can a value held in React state with useState or useReducer never tear during a concurrent render, while a value read from a mutable module-level object can?
answer
- derived per pass versus read live
- update queues, not mutable cells
- new updates schedule a future render
- abandoned renders are never committed
- refs are live cells too
basics
~20 sReact state is versioned, not live: one render pass derives state from each fiber's update queue, so every component it commits sees the same version. A mutable module object is read live, so two reads during one render can differ.
solid answer
~50 sBecause React state is not fetched out of a mutable cell at render time — it is derived. Each fiber carries a queue of pending updates, and a render pass applies exactly the updates that belong to the priorities it is rendering. The values are therefore fixed for that pass: an update arriving while the render is in progress cannot retroactively change what already-rendered components computed. It schedules more work instead, and React either restarts the render or performs another one, so whatever gets committed is a single coherent version of state. React also keeps the on-screen tree and the work-in-progress tree separate, so a half-finished render never reaches the user. A module-level object has none of that machinery: `store.value` is whatever it holds at the instant you read it, and two components reading at two instants can legitimately disagree.
go deeper
Know that values coming from useState or from props are safe, and that reaching into a shared object during render is the risky pattern. Be able to name which of the two a given line of code is doing.
Explain that a render pass derives state from each fiber's queued updates, so one pass produces one version, while a property read on a module object reflects whatever is true at that instant.
Demonstrate that you check the whole read surface, not just useState — refs read during render, mutated objects stored in state, and caches consulted from a component body all reintroduce the same live read.
Own the guidance: decide where shared mutable data may be read from, insist that render-time reads go through one sanctioned path, and be able to justify that constraint to a team that finds direct reads simpler.
## The difference in one sentence React state is *derived per render pass*; external data is *read live*. Everything else follows from that. ## How React state gets its value When you call a setter, React does not immediately overwrite a variable somewhere. It appends an update to a queue attached to that component's fiber — React's internal record of the component — and marks that work as pending. Later, when React renders, it walks the tree and, for each component, computes the state by replaying that fiber's queued updates. Two consequences matter here: 1. **The inputs to a render pass are decided when the pass begins.** A pass renders a chosen set of pending work. Updates that arrive afterwards belong to a *future* pass. They do not reach backwards into components already rendered in the current one. 2. **A pass never mixes versions.** Because state is recomputed from the queue by that pass, every component the pass renders sees state consistent with that pass, whether it was rendered in the first slice or the fifth. So even though the render was interrupted, split, and resumed, the state it committed is one coherent snapshot. ## What happens when an update lands mid-render Nothing sneaky. If a higher-priority update arrives while a render is in flight, React does not splice the new value into a half-finished tree — it abandons or defers that render and does the work again with the new information. The abandoned render is never committed, so the user never sees the mixture. Discarding work is exactly the price React pays for consistency, and it can afford it because a render pass produces no side effects on the DOM until commit. React also maintains two trees: the one currently on screen and the work-in-progress one it is building. Only a completed work-in-progress tree is swapped in. There is no moment where half the new tree is live. ## Why an external object cannot offer any of this Consider: ```js export const session = { user: null }; function Greeting() { return session.user ? `Hi ${session.user.name}` : 'Sign in'; } ``` When `Greeting` runs, `session.user` is evaluated right then. There is no queue, no version, no notion of "the value belonging to this render pass" — the property read reflects the object's state at that microsecond. If React renders `Greeting`, yields, some code assigns `session.user`, and React then renders a sibling that reads the same object, the two components have honestly read two different worlds and both results get committed. The object is not doing anything wrong. It simply has no way to answer the question React needs answered: *what was your value for the render that is currently in progress?* ## The same reasoning covers props Props are computed by a parent during the same render pass, from that parent's state and its own props. They inherit the pass's consistency, which is why a value threaded down from one owner cannot tear either. This is why "lift the value to one owner and pass it down" is a genuine fix and not merely a style preference — it converts an out-of-band read into a value produced by the render itself. ## The common misconceptions - *"Immutability is what saves useState."* Not quite. You can put a mutable object in state and mutate it later; then you are back to reading live data and can tear again. The protection comes from the value being decided by the render pass, not from the value's own immutability. - *"Batching prevents it."* Batching decides how many render passes happen. Consistency inside a pass is a separate guarantee and holds even for a single unbatched update. - *"Refs behave like state here."* They do not. A ref's `current` is a live mutable cell, exactly like a module variable. Reading `ref.current` during render is an out-of-band read and can disagree across a yield — which is one reason reading refs during render is discouraged. ## What to say in an interview State the mechanism, then the contrast: React state is recomputed by the render pass from queued updates, so one commit is one version; external data is read at whatever instant each component happens to run, and a concurrent render supplies several such instants. The problem is not that external data is mutable — it is that React has no way to pin it to a pass unless it is told about the read.
- If a state update arrives while a render is already in progress, what happens to the render?It does not get patched. React either finishes it and renders again for the new work, or abandons it and restarts, depending on the priority of what arrived. Either way, the tree that reaches the screen was produced by a single pass, so it cannot contain a mix of old and new state.
- Does putting a mutable object into useState make it safe from tearing?No. State protects the *binding* the render pass sees, not the contents of whatever you stored. If you mutate that object in place and components read its properties during render, you are reading live data again and the same inconsistency window reopens. Replace the object on update rather than mutating it.
- What about reading a ref's current value during render?A ref is a plain mutable cell, so reading `ref.current` during render is exactly the out-of-band read that can disagree across a yield. That is one of the reasons React discourages reading or writing refs during rendering and steers them toward effects and event handlers.
- Do props share the state guarantee?Yes. Props are computed by the parent during the same render pass, so they carry that pass's version of everything. That is why threading a value down from a single owner is a real fix for tearing and not just a stylistic preference — the value stops being read out of band.
saying these in an interview costs you the question
- useState is safe only because its values are immutable
- Batching is what makes React state consistent
- Refs behave like state, so reading ref.current during render is fine
- React patches already-rendered components when a new setState arrives
- Storing a mutable object in state makes that object tear-proof