Question presented to candidate: "The Rules of Hooks say call them at the top level and never conditionally. What is the actual implementation reason?"
What a strong answer should cover:
- React stores a component hook state in an ordered list on its fiber, and matches each call to its slot by position, not by name.
- There is no identifier available:
useState(0)gives React nothing to key on. The index in the call sequence is the identity. - A conditional call shifts every later hook onto the wrong slot — so a
useStatecan land on auseEffectslot, reading another hook's data. - React detects the count mismatch and throws: "Rendered fewer hooks than expected. This may be caused by an accidental early return statement."
- The same reasoning covers early returns, loops with variable counts, and hooks inside nested functions or conditions.
- Why the design: it keeps hooks a plain function call with no registration, no keys, and no boilerplate — an explicit trade of flexibility for ergonomics.
- The tooling angle: this constraint is what makes hooks statically analysable, which is why the ESLint rule can verify them and the React Compiler can memoise safely.
- The workaround: put the condition inside the hook, or extract a component.
use()is the deliberate exception — it may be called conditionally because it does not own a state slot.
Clarifying questions expected:
- "Do you want the failure mode, or why React chose positional matching over named slots?"
Code / implementation expected: Optional. A minimal hook implementation using an array and a cursor is the clearest way to show it.