React Curriculum

useState Internals, Lazy Init & State Resetting

Medium100% Free~25 mins#react#usestate#hooks#lazy-initialization#performance#fiber
Interview Answer (30-Second Summary)

useState persists state across renders in a linked list of hook slots attached to the component Fiber. Lazy initial state (useState(() => init())) runs expensive computations once on mount rather than on every render, and key props cleanly reset component state.

Key Learning Objectives
✓

Understand how React stores hook state in an internal linked list on the Fiber node.

✓

Implement lazy initial state to avoid expensive recalculations on every render.

✓

Reset state cleanly when props change using the key prop pattern.

✓

Avoid calling initial state calculation functions directly inside useState(init()).

The Interview Problem & Context

What is the difference between useState(expensiveComputation()) and useState(() => expensiveComputation())? Implement a stateful hook with lazy initialization and explain how React allocates state slots internally.

1function expensiveCompute(): number {
2 // Heavy computation (e.g. parsing 5MB JSON from localStorage)
3 return 42;
4}
5
6export function Counter() {
7 // ❌ BAD: expensiveCompute() runs on EVERY single re-render of Counter!
8 // const [count, setCount] = useState(expensiveCompute());
9
10 // ✅ GOOD: Lazy Initializer: function only invoked ONCE during initial mount!
11 const [count, setCount] = useState(() => expensiveCompute());
12
13 return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
14}
Deep Architectural Explanation

How useState Works Under the Hood

React keeps track of which hook belongs to which state value using an internal singly linked list of Hook objects stored on the component's Fiber node (fiber.memoizedState):

ts
type Hook = {
  memoizedState: any, // The current state snapshot
  baseState: any,
  queue: UpdateQueue, // Queued updates
  next: Hook | null,  // Pointer to the next hook in the component!
};

Every time your component executes, React steps through this linked list from top to bottom. This is the fundamental reason behind the Rules of Hooks: you cannot call hooks conditionally or in loops, because doing so shifts the index pointers in the linked list!

The Cost of Direct Initializer Invocations

When you write:

tsx
const [data] = useState(parseLargeJson());

In JavaScript, function arguments are evaluated before the function call. Even though React only uses the initial value on Mount 1 and discards it on every subsequent render, parseLargeJson() still runs on every single render!

By passing a callback:

tsx
const [data] = useState(() => parseLargeJson());

React only invokes the function during the initial mount, saving CPU cycles on all subsequent renders.

Common Interview Traps & Pitfalls
✕

Calling expensive functions directly inside useState: useState(localStorage.getItem(...)).

✕

Using useEffect to reset state when an ID prop changes instead of using a key prop on the component.

✕

Violating the Rules of Hooks by putting useState inside an if condition.

Senior Interviewer Follow-Up Probes
?

Why can't React use variable names to match hook states instead of call order?

?

What happens to the internal hook linked list if a component throws an error during render?

?

How does React 19 Action state (useActionState) build upon useState queue mechanics?

Frequently Asked Interview Questions
Why does useState(() => init()) only execute once?

Because React checks if the hook slot on the Fiber node is already initialized. If it is, React ignores the initializer function and returns the memoized state without calling the function.

The expensive() function will execute on every single re-render of the component, wasting CPU time, even though React throws away its return value on all renders after the first.

Because React matches hook state to components using the fixed order of execution in an internal linked list. Conditional or looped calls disrupt the order, mapping state to the wrong hook slots.

By changing the component's key prop (e.g., <Form key={userId} />). React treats a changed key as a new component identity, destroying the old Fiber state and mounting a fresh instance.

Yes, but you must use a functional setter or nested function: setState(() => myFunction), otherwise React will interpret the function as an updater and execute it immediately.