skip to content

Beyond re-running effects, what else does React's <StrictMode> deliberately invoke twice during a development render, and what kind of bug is that designed to expose?

level: middleimportance: should knowfreq 45%

answer

  1. React calls the pure functions twice
  2. component body, initializer, updater
  3. one result is kept, one ignored
  4. mutation outside render becomes visible
  5. concurrent rendering assumes the same purity

basics

~20 s

StrictMode also double-invokes the functions React expects to be pure: the component body itself, the initializer function passed to useState, and state updater functions. Running each twice makes impure code — mutation or side effects during render — produce visibly wrong results.

solid answer

~40 s

StrictMode double-invokes every function React treats as pure. That means the component function body, the lazy initializer you pass to `useState`, the updater functions you pass to a set function, and, for class components, the constructor and `render`. React keeps the result of one call and ignores the other. A pure function returns the same thing both times, so nothing changes; an impure one — pushing to an array declared outside the component, mutating a prop or a piece of existing state, incrementing a module-level counter, writing to `localStorage` during render — produces a doubled or corrupted result you can see immediately. The purpose is that React's concurrent features are allowed to call your render function speculatively, discard the work, and call it again, so anything that leaks out of render is already broken.

go deeper

for a junior

Know that the component function body itself runs twice under StrictMode in development, which is why a console.log in render prints twice, and that React expects render to have no side effects.

for a middle

List the double-invoked functions — component body, useState initializer, state updater, class constructor and render — and explain that React keeps one result and ignores the other, so only impure code shows a difference.

for a senior

Connect the check to why it exists: concurrent rendering may discard and redo render work, and server rendering runs the same component elsewhere. Show how you would hunt down and refactor the impure render code the doubling reports.

for a principal

Argue purity as an enabling constraint rather than a rule: it is the precondition for concurrent features, server rendering and build-time auto-memoization. Be ready to say how you keep a codebase inside that constraint as it grows.

## The rule being enforced React requires rendering to be pure: given the same props, state, and context, a component must return the same JSX and must not change anything outside itself while doing so. StrictMode enforces that rule the bluntest possible way — it calls the pure functions twice and lets the discrepancy speak. The functions covered are the ones React itself is entitled to call whenever it likes: - the component function body; - the lazy initializer passed to `useState(() => expensiveInit())`; - updater functions passed to a set function, as in `setCount(c => c + 1)`; - for class components, the constructor, `render`, and `shouldComponentUpdate`. React keeps one call's result and ignores the other. For a pure function that is invisible. For an impure one it is a loud failure. ## What the doubling actually catches The classic case is mutation of something that lives outside the render: ```jsx const rows = []; function Table({ items }) { items.forEach((item) => rows.push(item)); // impure: mutates module state return <ul>{rows.map((r) => <li key={r.id}>{r.name}</li>)}</ul>; } ``` Under StrictMode every row appears twice, immediately, on the first render. Without it the array quietly accumulates duplicates only when the component happens to render more than once, which may be days later and in production. The same detector catches mutating existing state or props during render: ```jsx function Cart({ items }) { items.sort((a, b) => a.price - b.price); // mutates the caller's array const [total, setTotal] = useState(0); total += items.length; // pointless, but a real habit return <List items={items} />; } ``` and side effects smuggled into render — writing to `localStorage`, firing an analytics call, appending a DOM node — all of which produce doubled observable output. The fix in every case is the same: create new values instead of mutating existing ones (`[...items].sort(...)`), and move anything that touches the outside world into an event handler or an effect. ## Why updater functions are included ```jsx setCount((c) => { log('updating'); // impure — you will see this twice return c + 1; }); ``` The updater must be a pure transformation of the previous state, because React may replay the queue of updates. Double-invoking it exposes updaters that log, mutate the previous state object in place, or otherwise assume they run exactly once. Note that the *result* is not doubled: the count still goes up by one, because React ignores one of the two calls. Only the impure side effect is visible twice, which is precisely the diagnostic. ## Why initializers are included ```jsx const [state, setState] = useState(() => createInitialState(props.seed)); ``` The lazy initializer exists so expensive setup happens once per mount rather than on every render. StrictMode calls it twice in development and ignores one result. If `createInitialState` is pure, the two results are equivalent and nothing changes. If it registers something, allocates a resource, or advances a shared counter, you learn immediately that it is not an initializer at all — it is a side effect that needs a different home. ## The connection to concurrent rendering This is not busywork. React's concurrent renderer may start rendering a component, abandon that work because something more urgent arrived, and render it again later. Server rendering renders your component in a different process entirely. The React Compiler's auto-memoization assumes the same purity. Every one of those depends on render being a function of its inputs with no observable trace. StrictMode is a cheap local approximation of what those systems will do to your component anyway. ## Practical notes Because the component body runs twice, a `console.log` written directly in render can print twice; that is the mechanism, not a separate feature. In React 19, StrictMode also exercises ref callbacks an extra time on initial mount, which is why callback refs are expected to be able to attach, detach, and attach again cleanly. And as with effects, none of this happens in a production build. StrictMode's whole surface is a development-time contract check; the shipped bundle calls each of these functions the normal number of times.

  • If the updater passed to a set function runs twice, why does the counter still only increase by one?
    Because React ignores one of the two results. The double invocation is a purity probe, not two applied updates: React computes the next state twice from the same previous state and commits one. Anything that changes by two — a log line, a mutated object, a module counter — is by definition a side effect that does not belong in the updater.
  • Does StrictMode double-invoke the function passed to useEffect's dependency-free callback in the same way it double-invokes render?
    No, and the distinction matters. Render functions, initializers and updaters are double-*invoked* because they must be pure. Effects are handled differently: React performs a whole extra mount cycle, running setup, then the cleanup you returned, then setup again. One tests purity, the other tests teardown.
  • How would you find impure render code that StrictMode is already reporting, in a large app?
    Start where the doubling is observable: duplicated list entries, counters that jump by two, duplicate network calls or storage writes at first paint. Then grep render bodies for mutation and outside-world calls — array `push`/`sort`/`splice` on props, assignments to module-level variables, `localStorage`, `document` writes. The React lint rules flag some of these, but the observable doubling is the fastest lead.

saying these in an interview costs you the question

  • Thinks only effects are double-invoked
  • Says the doubled updater makes state increase by two
  • Believes double rendering happens in production too
  • Calls render mutation harmless because it 'works'
  • Cannot name anything React expects to be pure

context