ReactBy Experience Levelintermediate
Updated:

React Interview Questions for 2 Years Experience

7 min read

The React questions asked of developers with around two years of experience — hooks in practice, useEffect, keys, controlled forms and the debugging you have actually done.

TL;DR – Quick Answer

At two years, React interviews move past definitions and probe how you actually build components: the rules of hooks, why useEffect needs a dependency array and cleanup, why lists need stable keys, controlled versus uncontrolled inputs, and lifting state up. Expect scenario questions grounded in code you have written, plus a couple of debugging cases — stale closures, infinite effect loops — that only show up once you have shipped real features.

On This Page

Two years into React, interviews stop rewarding textbook definitions and start testing whether you have actually built and debugged real components. The questions below are the ones that come up for developers in the one-to-three-year band — hooks in practice, effects, keys, forms and the small production bugs that only bite people who have shipped features. Answer them from your own experience, not from a tutorial.

How the 2-year React interview is graded

Interviewers at this stage are checking two things: that you can write idiomatic function components with hooks without hesitation, and that you understand why the rules exist rather than just following them. When you get a scenario question, narrate the render cycle — what triggers a re-render, what runs on every render, what runs only when dependencies change. That habit signals real experience faster than any single fact.

Q1. What are the rules of hooks, and why do they exist?

Hooks must be called at the top level of a component or another hook — never inside conditions, loops or nested functions — and only from React function components or custom hooks. React tracks hook state by call order, so calling them conditionally would misalign that order between renders and corrupt state.

The mechanism is the whole answer. React does not know the name of your state; it stores hook values in a list and matches them by the order they are called. Skip a useState on one render and every later hook shifts by one slot. That is why the linter treats a conditional hook as an error, not a warning.

// Wrong: hook inside a condition — breaks call-order tracking
if (isLoggedIn) {
  const [name, setName] = useState("");
}

// Right: hook at top level, condition inside
const [name, setName] = useState("");
if (isLoggedIn) { /* use name */ }

Interview note: Follow-up: "how do you run a hook conditionally then?" You do not — you move the condition inside the hook, or split it into a separate component that only mounts when the condition is true.

Q2. Explain the useEffect dependency array. What goes wrong when you get it wrong?

The dependency array tells React when to re-run an effect: empty means once after mount, a populated array means re-run when any listed value changes, and omitting it means run after every render. Every reactive value the effect reads should be listed — missing one causes stale reads, and including an unstable value causes an infinite loop.

This is the single most-probed hook at two years because it is where real bugs live. If your effect reads count but count is not in the array, the effect keeps seeing the value from the render where it was created — a stale closure. If you put an object or function literal in the array, it is a new reference every render, so the effect fires forever.

useEffect(() => {
  const id = setInterval(() => setTick(t => t + 1), 1000);
  return () => clearInterval(id); // cleanup: prevents leaks and double timers
}, []); // empty: set up once, tear down on unmount

Interview note: Trap: "why use the updater form setTick(t => t + 1) instead of setTick(tick + 1)?" Because the updater reads the latest state, avoiding the stale tick captured by the empty-array closure.

Q3. Why do lists need a key, and why not the array index?

Keys let React match elements between renders so it can reorder, insert and remove efficiently instead of rebuilding the list. The key must be stable and unique per item — a database id is ideal. The array index breaks the moment the list is reordered or filtered, because item and position stop lining up, causing wrong state and visual glitches.

The classic bug: a list of inputs keyed by index. Delete the first row and every input below shifts up, but React thinks the same keys are still present, so the typed values stay attached to the wrong rows. Keying by a stable id fixes it instantly.

Interview note: Follow-up: "when is the index acceptable?" Only for a static list that never reorders, filters or changes length — otherwise treat it as a bug.

Q4. Controlled vs uncontrolled inputs — which do you use and why?

A controlled input has its value driven by React state and updated through onChange, so React is the single source of truth. An uncontrolled input keeps its own value in the DOM and you read it with a ref when needed. Controlled is the default because it makes validation, conditional disabling and derived UI trivial; uncontrolled suits simple or performance-sensitive forms.

function EmailField() {
  const [email, setEmail] = useState("");
  return (
    <input
      value={email}
      onChange={(e) => setEmail(e.target.value)}
      aria-invalid={!email.includes("@")}
    />
  );
}

Interview note: Trap: "you set value but no onChange — what happens?" The field becomes read-only and React warns; you either add onChange or use defaultValue for an uncontrolled field.

Q5. What does "lifting state up" mean, and when do you do it?

When two sibling components need the same piece of state, you move that state up to their closest common parent and pass it down as props, with callbacks to update it. This keeps a single source of truth instead of two copies that drift out of sync.

At two years this is the answer to most "how do these two components talk?" questions. Only when the state needs to reach many distant components does lifting become prop-drilling pain — that is the cue to reach for Context or a store, which is a natural bridge to the state management questions.

Interview note: Follow-up: "when is lifting the wrong move?" When the state is truly local — a dropdown's open/closed flag does not belong in a parent just because it could.

Q6. What is a stale closure, and how have you hit one?

A stale closure happens when a function — often inside useEffect or an event handler — captures a state or prop value from the render where it was created, then runs later and uses that outdated value. It is the most common React bug once you use timers, subscriptions or async callbacks.

The fixes are worth naming: use the functional updater form of setState, add the value to the dependency array so the closure is recreated, or store the latest value in a ref. Being able to describe a real time you debugged one — "my interval logged the initial count forever" — is exactly what this level wants.

Q7. How do you fetch data in a component correctly?

Trigger the fetch in a useEffect keyed on the inputs that should refetch, guard against setting state after unmount or after a newer request, and handle loading and error states explicitly. The race condition is the interesting part: if the user changes the query quickly, an earlier slow response can overwrite a newer one.

useEffect(() => {
  let active = true;
  fetch(`/api/users/${id}`)
    .then(r => r.json())
    .then(data => { if (active) setUser(data); });
  return () => { active = false; }; // ignore stale responses
}, [id]);

Interview note: Follow-up: "what would you use in a real app?" A data library like React Query or the framework's data layer, which handle caching, dedup and races for you — but you should still understand the raw version.

Q8. How does React decide to re-render, and why did your component render "too much"?

A component re-renders when its state changes, its parent re-renders, or its context value changes. Passing a new object or function as a prop on every render, or updating high-up state on every keystroke, causes cascades of child renders. The tools are React.memo for components, useMemo for expensive values and useCallback for stable function props — but only after you have measured, not by default.

Interview note: Trap: "does re-render mean the DOM updated?" No — React re-runs the component and diffs; the DOM only changes where the output actually differs. Cheap renders are fine; the goal is correctness first.

Q9. Map the old class lifecycle to hooks.

componentDidMount and componentDidUpdate together become useEffect with the right dependencies; componentWillUnmount becomes the cleanup function returned from the effect; and getDerivedStateFromProps usually becomes computing a value during render or a key reset. You still meet class lifecycle in older code, which is why the lifecycle methods questions remain worth a pass even in a hooks-first shop.

Interview note: Follow-up: "is one effect equal to didMount plus didUpdate?" An effect with a dependency array runs after mount and after any listed change — so yes, one effect can cover both, which is cleaner than the split class version.

How to prepare

Rebuild two features you have actually shipped and be ready to talk through them: where state lived, which effects ran, one race or stale-closure bug you fixed, and what you would restructure now. Then drill the four highest-yield topics here — the dependency array, keys, controlled inputs and re-render causes — until you can both explain and fix each live. When you feel solid, look ahead at the 3-year question set to see how the same topics get pushed into custom hooks and performance, and use a focused mock interview to practice narrating your reasoning under follow-up pressure. Structured hooks practice is available in the React learning path.

Frequently Asked Questions

What level of React is expected at 2 years of experience?
You are expected to write function components with hooks fluently, understand why the rules of hooks exist, handle side effects and cleanup correctly, and manage form and list state without help. Deep architecture is not expected yet, but 'I have used it' answers should be backed by how and why.
Do I need to know class components for a 2-year React interview?
You should recognise class lifecycle methods and be able to map componentDidMount and componentWillUnmount to useEffect, because older codebases still use them. Most new questions center on hooks, but the mapping question is common.
How much of Redux is asked at this level?
Usually just enough to explain when you would reach for it versus Context or local state, and the basic flow of action to reducer to store. Deep middleware and normalization questions are more common at three-plus years.
What is the most common React mistake interviewers probe at 2 years?
The useEffect dependency array — either omitting dependencies and reading stale values, or including a value that changes every render and causing an infinite loop. Being able to explain and fix both live is a strong signal.
How should I talk about projects in a 2-year React interview?
Pick one or two features you owned, explain the component structure, where state lived, one bug you debugged, and one decision you would make differently now. Concrete ownership beats a long tool list.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Discover CodeBegun's Java Full Stack track

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 16 July 2026 LinkedIn
Chat with us