Engrain by ByteRush

RCT.01.02 Mid-level· 10 minCore — asked often

State as a snapshot, and batching: why setCount(count + 1) three times adds one

Each render sees a fixed snapshot of state. Learn updater functions, how React queues and batches updates (everywhere since React 18), when to use flushSync, and how to predict the result of several updates.

Comes up in: React, Recruiter screen, Coding

Learn#

Pallavi’s “Add 3 items” button calls setCount(count + 1) three times. The cart shows 1, not 3, and the console.log right after shows the old value.

State is a snapshot: each render gets fixed values, and event handlers created in that render see those values. setCount does not change count in the current code; it queues an update for the next render.

An updater function, setCount(c => c + 1), receives the pending value, so updates build on each other:

import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register(); // a DOM for Node; in a browser you already have one
const { createElement: h, useState } = await import("react");
const { createRoot } = await import("react-dom/client");
const { flushSync } = await import("react-dom");

let renders = 0;
let addThree;
function Cart() {
  const [count, setCount] = useState(0);
  renders++;
  addThree = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount((c) => c + 1);
    console.log("in the handler, count is still", count);
  };
  return h("p", null, `items: ${count}`);
}
const box = document.createElement("div");
const root = createRoot(box);
flushSync(() => root.render(h(Cart)));
flushSync(() => addThree());
console.log(box.textContent, "| renders:", renders);
setTimeout(addThree, 0);
await new Promise((r) => setTimeout(r, 50));
console.log(box.textContent, "| renders:", renders);
root.unmount();
This example uses Node.js, so it runs in our checks rather than in your browser.

Output (verified)

in the handler, count is still 0
items: 2 | renders: 2
in the handler, count is still 2
items: 4 | renders: 3

Three updates caused one render each time, even inside setTimeout. That is batching: React waits until your code finishes, then renders once.

Three updates, one renderStep 1 of 4

State

  • snapshot: count = 0

Console

    empty

    Page

      empty

      This render's snapshot: count = 0. The click handler was created during this render, so it sees 0.

      Read the steps as text
      1. This render's snapshot: count = 0. The click handler was created during this render, so it sees 0.
      2. setCount(count + 1) queues "replace with 1". setCount(count + 1) again queues "replace with 1".
      3. setCount(c => c + 1) queues an updater function. The handler logs count: still 0.
      4. After the handler, React processes the queue in order: 1, 1, then 1 + 1 = 2. One render shows items: 2.

      Predict#

      Predict first

      React 18+: inside a fetch().then callback you call setA(1) and setB(2). How many renders?

      Interview angle#

      Say it like this — about 30 seconds

      “setState does not change the variable right away. It queues an update and schedules a render, and the current render keeps its snapshot, so the handler still sees the old value. Calling setCount(count + 1) three times queues the same replacement three times, so it adds one. The updater form, setCount(c => c + 1), works on the pending value, so three of them add three. Since React 18, updates in the same task are batched into one render everywhere. flushSync forces an immediate render when I really need it.”

      Stays on your device. Nothing is uploaded.
      Follow-up What is automatic batching?
      React 18+ combines all state updates from the same task, in handlers, timeouts or promises, into one render.
      Follow-up What happens when you call setState? Is it synchronous?
      It queues an update and schedules a render; the current variable does not change, so in that sense it is asynchronous.
      Follow-up Why does setCount(count + 1) three times only add one?
      Each call uses the same snapshot value, so all three say “set to count + 1”. Use the updater form.

      Practice#

      Guided

      Compute what React does with a queue of updates, and how many renders batching produces.

      Done when

      • Values replace the pending state; functions update it, in order.
      • Each task with updates causes one render.
      • Each flushSync call renders on its own.
      Your solution
      Ctrl/⌘ + Enter

      Remember#

      Try to answer each card before you open it. Then add them to your reviews.

      What does a handler see when it reads state after calling setState?

      The old value: this render's snapshot.

      setCount(c => c + 1) three times in one handler adds how much?

      Three.

      Since which version does React batch updates in timeouts and promises?

      React 18, with createRoot.

      What does flushSync do?

      Forces React to apply the updates inside it and render immediately.

      Go deeper

      Four layers. Stop at the one your interview needs; each ends with a quick check.

      The core ideaEvery round

      State lives inside React, attached to the component; each render reads a copy of it. Closures created in a render keep that render’s values, like the closures in JS.02.03.

      Check yourself: Where does React keep state between renders?

      Inside React, attached to the component's position in the tree, not in your variables.

      How it works underneathSenior rounds
      React processes the queue in order: a value replaces the pending state, a function receives it. So setCount(5); setCount(c => c + 1) gives 6 Source: React: Queueing a series of state updates.
      Check yourself: In what order does React process queued updates?

      In the order they were queued; values replace, functions transform the pending value.

      Edge cases and trapsWhere people slip

      Updater functions must be pure: in Strict Mode development React calls them twice. Batching applies within one task; two separate clicks are two renders.

      Check yourself: Why should updater functions be pure?

      In Strict Mode development React calls them twice to catch side effects.

      In productionStaff-level answers
      flushSync forces an immediate render, for example to scroll to a just-added item. It hurts performance, so use it rarely Source: React: flushSync.
      Check yourself: When is flushSync justified?

      Rarely: when you must read updated DOM right away, such as scrolling to a just-added item.

      Finished the five steps? Mark it, and its cards join your reviews.

      Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

      Interview questions this answers

      • What is automatic batching?
      • What happens when you call setState? Is it synchronous?
      • Why does setCount(count + 1) three times only add one?