Home Interview React Keys Bug — Input Focus Lost on List Reorder
Intermediate 9 min · March 06, 2026
React Interview Questions

React Keys Bug — Input Focus Lost on List Reorder

Prepend an item and the text cursor jumps to the wrong input.

N
Naren Founder & Principal Engineer

20+ years shipping production code across the stack, with years spent interviewing engineers. Drawn from code that ran under real load.

Follow
Production
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 25 min
  • Solid grasp of fundamentals
  • Comfortable reading code examples
  • Basic production concepts
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • React renders UI declaratively: you describe state, React handles the DOM
  • Reconciliation diffs the virtual DOM in O(n) using heuristics (element type + key)
  • Hooks rules ensure consistent execution order across renders
  • Controlled components store form state in React, uncontrolled in DOM refs
  • Performance trap: inline arrow functions break memoisation via useCallback/React.memo
✦ Definition~90s read
What is React Interview Questions?

This article dissects the specific React interview questions that separate engineers who've merely read the docs from those who've debugged production apps at 2 AM. It's not a list of trivia — it's a field guide to the five topics that actually matter when you're hiring someone to build and maintain a real React application at scale.

Think of React like a smart whiteboard in a classroom.

The focus is on the underlying mechanisms (virtual DOM diffing, reconciliation, closure traps in hooks) rather than surface-level syntax, because the interview isn't about whether someone can write a component — it's about whether they understand why it breaks and how to fix it without rewriting the whole thing.

The questions covered here target the most common failure modes in production React: lost input focus during list reordering (the keys bug), stale closures in useEffect, unnecessary re-renders from misuse of useCallback/useMemo, and the controlled-vs-uncontrolled boundary that silently corrupts form state. These aren't academic — they're the bugs that slip through code reviews and cause real user data loss or performance degradation.

The article explains each through the lens of React's actual execution model, not memorized answers.

If you're preparing for a senior React role, this is the material that distinguishes you from someone who can build a todo app. If you're interviewing, these are the questions that expose whether a candidate has felt the pain of debugging a re-render loop or just read about it. The article assumes you already know JSX and state — it's about the hard parts that don't fit in a tweet.

Plain-English First

Think of React like a smart whiteboard in a classroom. Instead of erasing and redrawing everything every time something changes, it only erases and redraws the parts that actually changed. That's React's whole superpower — it's incredibly efficient about what it updates on screen. The 'rules' React has (like hooks rules and component structure) are just the whiteboard's instructions for how to keep track of what changed and why.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

React is the most popular front-end library in the world right now, and that means every JavaScript developer eventually walks into an interview room and faces React questions. The problem isn't that these questions are hard — it's that most developers know HOW React works but can't explain WHY it works that way. Interviewers can smell the difference in about 30 seconds. If you can only recite syntax, you'll get filtered out. If you can explain the reasoning behind design decisions, you get the offer.

React solves the messy problem of keeping your UI in sync with your data. Before React, developers manually poked at the DOM and prayed nothing got out of sync. React introduced a declarative model: you describe what the UI should look like given your current state, and React figures out the most efficient way to make it happen. It's the difference between telling someone 'make the button red' versus giving them step-by-step painting instructions every single time.

By the end of this article you'll be able to answer questions about the virtual DOM, reconciliation, hooks rules, controlled vs uncontrolled components, and performance optimization — and more importantly, you'll understand the reasoning deeply enough to handle follow-up questions you've never seen before. That's what actually passes interviews.

What React Interview Questions Actually Test

React interview questions assess your understanding of React's core mechanics: component lifecycle, state management, and the virtual DOM diffing algorithm. They probe beyond syntax into how React reconciles UI updates efficiently. The key mechanic is the reconciliation process — when state or props change, React builds a new virtual DOM tree and diffs it against the previous one to determine minimal real DOM mutations. This is O(n) for typical trees due to heuristics like keyed list comparison.

In practice, the most critical property is the key prop on list elements. Keys allow React to match children across renders, preserving component identity and state. Without stable keys, React defaults to index-based matching, which breaks on reorder, insertion, or deletion — causing unnecessary unmounts and remounts. This is why input focus loss occurs: the component instance is destroyed and recreated, losing its internal state like cursor position.

Use stable, unique keys (like database IDs) for any dynamic list. This matters in real systems because it prevents subtle bugs in forms, drag-and-drop interfaces, and real-time feeds. A senior engineer must understand that keys are not just for performance — they are correctness requirements for component identity.

⚠ Index as Key Is a Bug
Using array index as key on a reorderable list will cause input focus loss and stale state because React reuses components based on position, not identity.
📊 Production Insight
A drag-and-drop kanban board where cards lose their text input focus after reordering because index keys cause component remount.
The symptom: user types in a card, drags it to a new column, and the input loses focus — the component instance is destroyed and recreated.
Rule: always use a unique, stable identifier (e.g., database ID) as the key for any list that can be reordered, filtered, or appended.
🎯 Key Takeaway
Keys are for identity, not just performance — they determine which components are preserved across renders.
Stable keys prevent input focus loss, scroll position reset, and stale state in dynamic lists.
Never use array index as key if the list order can change — it breaks React's reconciliation assumptions.
react-interview-questions React Reconciliation Architecture Layered view of how keys affect component identity User Interface Input Component | List Container React Virtual DOM Component Tree | Key Mapping Reconciliation Engine Diff Algorithm | Key Comparison DOM Updates Node Replacement | Attribute Update Browser Focus Management Focus State | Input Selection THECODEFORGE.IO
thecodeforge.io
React Interview Questions

The Virtual DOM and Reconciliation: The Engine Under the Hood

The most common React interview question is 'What is the Virtual DOM?' but the follow-up 'How does reconciliation work?' is where most candidates stumble. React doesn't just refresh the page; it maintains a lightweight copy of the real DOM in memory. When state changes, React creates a new virtual tree and compares it (diffing) with the previous one.

Interviewers look for an understanding of the O(n) heuristic algorithm React uses. It assumes that two elements of different types will produce different trees and that a 'key' prop helps identify which elements are stable across renders. This prevents unnecessary re-renders and keep the UI snappy.

io/thecodeforge/react/ReconciliationDemo.jsxJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React, { useState } from 'react';

/**
 * TheCodeForgeReconciliation & Key Demo
 * This illustrates why 'keys' matter in the Virtual DOM.
 */
const ListManager = () => {
  const [items, setItems] = useState(['Docker', 'Kubernetes', 'Spring Boot']);

  const addTech = () => {
    // Adding to the start of the array to test React's diffing
    setItems(['React', ...items]);
  };

  return (
    <div className="p-4">
      <button 
        onClick={addTech} 
        className="bg-blue-500 text-white px-4 py-2 rounded"
      >
        Add React to Stack
      </button>
      <ul className="mt-4">
        {items.map((item, index) => (
          // BAD: Using index as key. React might lose state if items shift.
          // GOOD: Using the item string (or a unique ID) as the key.
          <li key={item} className="border-b py-1">
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ListManager;
Output
Component renders a button that prepends 'React' to a list, using unique keys for efficient DOM updates.
Try it live
🔥Forge Tip: Diffing Heuristics
In an interview, mention that while a full tree comparison is O(n³), React uses a heuristic O(n) approach. This shows you understand the engineering trade-offs behind the library's performance.
📊 Production Insight
Using index as key in lists causes component state to misalign when items reorder.
Production debug: check keys via React DevTools or console.log each key in render.
Rule: keys must be unique and stable across renders — use item IDs, never indices.
🎯 Key Takeaway
Reconciliation uses keys to match virtual DOM nodes across renders.
Bad keys cause stale or wrong component state.
Rule: always use stable unique keys for dynamic lists.
Choosing the Right Key for Lists
IfItems have a unique 'id' field from API
UseUse key={item.id} — preferred, stable across renders
IfItems are static, never reordered or filtered
UseIndex key is acceptable but still risky — avoid if possible
IfItems are dynamic (insert/delete/sort)
UseMust use unique ID, never index

Hooks Rules: Why They Exist and What Breaks When You Break Them

React hooks enforce two rules: only call hooks at the top level, and only call them from React functions (components or custom hooks). The 'why' is critical for interviews. Under the hood, React relies on the order of hook calls between renders to maintain state and effect references. If you conditionally call a hook, the number of hooks changes between renders, and React's internal list of hook states gets misaligned — leading to bugs like stale state or skipped effects.

Mid-level developers can recite the rules; senior developers explain the linked-list based hook storage mechanism. React stores hooks for a component as a linked list where each hook's pointer depends on the call order. Conditional calls break the order, corrupting the list.

io/thecodeforge/react/HookRulesViolation.jsxJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useState, useEffect } from 'react';

/**
 * TheCodeForgeHook Rules Violation Demo
 * This demonstrates what happens when you break the rules.
 */
const CounterWithCondition = ({ initial }) => {
  const [count, setCount] = useState(initial);
  // BAD: conditionally calling hook
  if (count > 5) {
    const [flag, setFlag] = useState(false); // WRONG — violates top-level rule
  }
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
};

export default CounterWithCondition;
// Error: Hooks must not be called conditionally
Output
React throws an invariant violation: 'Hooks can only be called inside the body of a function component.'
Try it live
⚠ Senior Interview Tip: Hook Storage as Linked List
Explain that React maintains a linked list of hook nodes per component. Each hook's next pointer depends on call order. Conditional calls break the chain, causing state mismatches.
📊 Production Insight
Breaking hook rules usually surfaces as 'Rendered more hooks than during the previous render'.
Debugging: check for early returns before hook calls or conditional hooks inside components.
Rule: keep all hooks unconditional and at top level — no exceptions.
🎯 Key Takeaway
React stores hooks in order — a linked list tied to render number.
Conditional hooks break the order and corrupt internal state.
Rule: never call hooks inside conditions, loops, or nested functions.
react-interview-questions Index Key vs Unique Key on Reorder Comparison of focus behavior with different key strategies Index as Key Unique ID as Key Component Identity Swapped on reorder Preserved on reorder Focus Retention Lost after reorder Maintained after reorder DOM Mutation Unnecessary replacements Minimal updates Performance Worse due to extra re-renders Optimal reconciliation Bug Risk High for dynamic lists Low with stable keys THECODEFORGE.IO
thecodeforge.io
React Interview Questions

Controlled vs Uncontrolled Components: The Production Reality

In interviews, you'll be asked the difference between controlled and uncontrolled components. Controlled components keep form state in React state (single source of truth). Uncontrolled components store form data in the DOM itself, and you access it via refs when needed (e.g., form submission). The choice matters for validation, real-time UI updates, and testability.

Most mid-level devs can define both. Senior devs discuss the tradeoffs: controlled components give you full control but cause a re-render on every keystroke (fine for most forms). Uncontrolled components are lighter but harder to react to changes. In production, you'll use controlled for most inputs, uncontrolled for file inputs or when you need to integrate with non-React code.

io/thecodeforge/react/ControlledVsUncontrolled.jsxJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React, { useState, useRef } from 'react';

/**
 * TheCodeForgeControlled vs Uncontrolled Example
 */
const FormExample = () => {
  // Controlled input with state
  const [email, setEmail] = useState('');

  // Uncontrolled input with ref
  const passwordRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const password = passwordRef.current.value;
    console.log('Controlled email:', email);
    console.log('Uncontrolled password:', password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Email (Controlled):</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <div>
        <label>Password (Uncontrolled):</label>
        <input
          type="password"
          ref={passwordRef}
          defaultValue=""
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

export default FormExample;
Output
A form with one controlled field (email) updating on keystroke, and one uncontrolled field (password) accessed via ref on submit.
Try it live
Mental Model
Mental Model: State Ownership
Think of controlled components as React owning the data; uncontrolled as the DOM owning the data.
  • Controlled: value + onChange === React is the sole source of truth.
  • Uncontrolled: defaultValue + ref === DOM holds the current value, React only reads when needed.
  • When you need to react to every change (e.g., live validation) → controlled.
  • When you only need the value at submit (e.g., password) → uncontrolled is simpler.
  • File inputs are always uncontrolled — you can't set the file path via state.
📊 Production Insight
Switching a controlled input to uncontrolled mid-lifecycle causes React to warn and ignore future state updates.
Debug: check if you're mixing defaultValue and value props on the same input.
Rule: pick one pattern per input — don't switch between controlled and uncontrolled.
🎯 Key Takeaway
Controlled: React state drives the input value.
Uncontrolled: DOM stores the value, ref reads it.
Rule: controlled for validation/real-time; uncontrolled for simple reads or file inputs.

useEffect and Lifecycle: The Cleanup Trap

useEffect is the gateway to side effects in React components. It replaces componentDidMount, componentDidUpdate, and componentWillUnmount — but with a functional twist. The function passed to useEffect runs after every render by default, unless you specify a dependency array. The cleanup function returned from the effect runs before the effect re-runs and on unmount.

Interviewers probe for understanding of the dependency array and cleanup. Common pitfalls: missing dependencies causing stale closures, omitting cleanup leading to memory leaks (e.g., subscriptions or timers).

io/thecodeforge/react/useEffectCleanup.jsxJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useState, useEffect } from 'react';

/**
 * TheCodeForge — useEffect with Cleanup
 * Tracks online/offline status using a browser event.
 */
const OnlineStatus = () => {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    // Cleanup function: removes event listeners to prevent memory leak
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []); // Empty array: effect runs once (mount) and cleanup on unmount

  return (
    <div>
      <p>You are currently: {isOnline ? 'Online' : 'Offline'}</p>
    </div>
  );
};

export default OnlineStatus;
Output
Shows online status that updates when browser connectivity changes. Event listeners are properly cleaned up on unmount.
Try it live
⚠ Common Cleanup Mistake
Missing cleanup in useEffect leads to memory leaks. Always return a cleanup function when you add subscriptions, timers, or event listeners.
📊 Production Insight
Missing cleanup causes duplicate effect runs and stale connections.
Debug: use React DevTools to inspect effect instances or browser performance profiler for accumulating listeners.
Rule: if you subscribe in useEffect, return an unsubscribe/cleanup function.
🎯 Key Takeaway
useEffect merges mount, update, and unmount into one hook.
Always return cleanup for subscriptions — memory leaks are silent.
Rule: dependency array must list every reactive value used inside the effect.

Performance Optimisation: memo, useMemo, useCallback

React's reconciliation is fast, but unnecessary re-renders drag down performance in complex apps. The interview topic: when and how to prevent wasteful re-renders. React.memo wraps a component to skip re-render if props haven't changed (shallow comparison). useMemo memoises expensive computation results. useCallback memoises callback functions to maintain referential stability across renders.

Interviewers like to see that you understand when NOT to use these tools. Over-optimisation can make code harder to debug and even hurt performance by holding onto large memoised objects. The key is: only memoise when you've measured a re-render bottleneck.

io/thecodeforge/react/PerformanceOptimisation.jsxJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { useState, useMemo, useCallback } from 'react';

/**
 * TheCodeForgeMemoisation Patterns
 * Optimising a component that renders a large filtered list.
 */
const ExpensiveList = React.memo(({ items, onToggle }) => {
  console.log('ExpensiveList rendered');
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onToggle(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

const App = () => {
  const [filter, setFilter] = useState('');
  const [items] = useState([/* large dataset */]);

  // useMemo: avoid recomputing filtered list on every render
  const filteredItems = useMemo(
    () => items.filter(item => item.name.includes(filter)),
    [items, filter]
  );

  // useCallback: stable reference for the callback so React.memo can work
  const handleToggle = useCallback((id) => {
    console.log('Toggled item', id);
  }, []);

  return (
    <div>
      <input
        placeholder="Filter"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      />
      <ExpensiveList items={filteredItems} onToggle={handleToggle} />
    </div>
  );
};

export default App;
Output
Filter input updates state; memoised expensive list only re-renders when filtered items or callback reference changes.
Try it live
🔥Forge Tip: Profiling First
Never optimise prematurely. Use React DevTools Profiler to identify actual re-render hotspots before adding memo, useMemo, or useCallback.
📊 Production Insight
Overusing useMemo can retain large arrays in memory longer than needed.
Debug: use browser performance tools to confirm memoisation reduces renders.
Rule: profile first, then memoise only the bottlenecked computations and callbacks.
🎯 Key Takeaway
React.memo stops re-renders when props haven't changed (shallow).
useMemo caches computed values; useCallback caches function references.
Rule: memoise only after profiling — premature optimisation wastes memory and confuses readers.

How Does React Actually Work? (The Bits That Matter in Production)

Interviews love this question because it's the difference between someone who read a tutorial and someone who's debugged a render loop at 2 AM.

React works because it doesn't touch the real DOM until it absolutely has to. Your components return JSX, which Babel compiles into React.createElement() calls. Those create a tree of plain JavaScript objects — the Virtual DOM. When state changes, React builds a new tree, diffs it against the previous one (reconciliation), and computes the minimal set of DOM mutations.

The key insight: reconciliation isn't free. The algorithm is O(n) based on heuristics — same position in the tree? Reuse. Different type? Tear down and rebuild. That's why key props matter. Slap a random index on a list and React will re-render every child when you delete the first item. Use a stable ID and it skips the entire subtree.

One-way data flow isn't a philosophy — it's a constraint that makes your life easier. Data goes down via props, events come back up via callbacks. No magic two-way binding, no surprise mutations. Predictable state changes mean predictable bugs.

ReconciliationPitfall.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — interview tutorial

// Bad: index as key re-renders everything on reorder
function OrderList({ orders }) {
  return orders.map((order, idx) => (
    <OrderCard key={idx} order={order} />
  ));
}

// Good: stable ID only re-renders changed items
function OrderList({ orders }) {
  return orders.map(order => (
    <OrderCard key={order.transactionId} order={order} />
  ));
}
Output
Bad: removing first order re-renders all cards.
Good: removing first order re-renders only the removed card.
⚠ Production Trap:
Using index as key in a sortable/filterable list will cause stale state bugs and unnecessary re-renders. Always use unique, stable identifiers.
🎯 Key Takeaway
React's reconciliation algorithm keys on position and type — give it stable keys or watch your perf burn.

JSX: The Syntax Your Interviewer Expects You to Actually Understand

JSX isn't HTML. It's syntactic sugar for React.createElement(type, props, ...children). Every interview question about JSX is really asking: do you understand the compilation step, or do you think React runs raw HTML?

When you write <div className="card">Hello</div>, Babel turns it into: React.createElement('div', { className: 'card' }, 'Hello')

That className is a dead giveaway — JSX uses the DOM API property names, not HTML attributes. htmlFor instead of for, tabIndex instead of tabindex. These catch people who never read the compiled output.

The {} for JavaScript expressions is because JSX is just function calls. Anything inside those braces must be an expression — not a statement. You can't write if inside JSX. You use ternary or logical &&. That's not a design choice, it's the compiler screaming at you.

Strings in JSX are auto-escaped against XSS. Raw HTML requires dangerouslySetInnerHTML — and yes, the name is intentionally ugly. Don't use it unless you've sanitized the input server-side.

JSXCompilation.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — interview tutorial

// What you write
function Greeting({ user }) {
  return <h1 id="title">Welcome, {user.name}</h1>;
}

// What Babel compiles it to
function Greeting({ user }) {
  return React.createElement(
    'h1',
    { id: 'title' },
    'Welcome, ',
    user.name
  );
}
Output
React.createElement returns a plain object:
{ type: 'h1', props: { id: 'title', children: ['Welcome, ', user.name] } }
🔥Senior Shortcut:
Memorize the HTML-to-JSX attribute mapping: classclassName, forhtmlFor, tabindextabIndex. Interviewers watch for these slip-ups.
🎯 Key Takeaway
JSX is a compile target for createElement calls — every HTML-like attribute maps to a real DOM property, not an HTML attribute.

Components: Functions That Return UI (And Nothing Else)

A React component is a function that takes props and returns JSX. That's it. The entire "component architecture" hype boils down to: compose small functions into larger ones. Class components were the old way — stateful, lifecycle-heavy, and verbose. Functional components with hooks are the present.

The difference matters in interviews. Class components have this, lifecycle methods, and more boilerplate. Functional components have hooks and no this confusion. But the real question is: why do functional components outperform class components in production?

Functional components are lighter. No this binding overhead. No instance creation. Hooks let you colocate state and effects without scattering logic across lifecycle methods. A class component needs componentDidMount, componentDidUpdate, and componentWillUnmount to handle side effects. A functional component uses one useEffect with a cleanup function.

Interviewers ask this to see if you've shipped both patterns. Say you prefer functional — then explain why: less code, easier testing, better tree-shaking. Class components aren't going anywhere in legacy codebases, but you wouldn't start a new one with them.

ClassVsFunction.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — interview tutorial

// Class component (legacy)
class UserProfile extends React.Component {
  componentDidMount() {
    fetchUser(this.props.userId);
  }
  render() {
    return <div>{this.props.name}</div>;
  }
}

// Functional component (modern)
function UserProfile({ userId, name }) {
  useEffect(() => {
    fetchUser(userId);
  }, [userId]);
  return <div>{name}</div>;
}
Output
Functional version: 14 lines vs 22 lines for class.
Less indirection, no 'this', dependency array is explicit.
💡Interview Gold:
When asked 'functional vs class', don't just pick one — explain that hooks eliminated the lifecycle verbosity and made state logic reusable across components.
🎯 Key Takeaway
Components are functions that return JSX. Functional + hooks is the production standard; class components are legacy baggage.

When should you use Redux over Context API?

Redux and Context API both solve state sharing. They are not interchangeable. Context is a dependency injection mechanism, not a state manager. When a value changes, every consumer re-renders. That's fine for theme or locale. It's a disaster for a shopping cart with 500 items.

Redux gives you granular subscriptions. Only components that depend on specific slices of state re-render. It also enforces a unidirectional data flow with reducers, which makes debugging and testing predictable. You don't reach for Redux because you have global state. You reach for Redux because you have complex state transitions, middleware needs, or performance requirements that Context can't meet.

Production rule: if your state changes less than once per second and affects fewer than 10 components, Context is fine. If those numbers climb, or if you need time-travel debugging, pick Redux.

WhenContextHurts.tsxPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — interview tutorial

// All 500 product cards re-render every time cart changes
const CartContext = React.createContext();

function CartProvider({ children }) {
  const [cart, setCart] = useState({items: [], total: 0});
  return (
    <CartContext.Provider value={{cart, setCart}}>
      {children}  {/* Every consumer re-renders */}
    </CartContext.Provider>
  );
}

// Fix: Redux useSelector subscribes to cart.items only
// const items = useSelector(state => state.cart.items);
// Components that read only `total` won't re-render
Output
500 re-renders on single item add. Latency visible to user.
⚠ Production Trap:
Context Provider wrapping a tree with 200+ components causes cascading re-renders. Profile with React DevTools before blaming the state library.
🎯 Key Takeaway
Context is for low-frequency, low-complexity state. Redux is for high-frequency, high-complexity state with performance demands.

How would you optimize a slow React application?

Before touching a single line of code, profile. Use React DevTools Profiler and browser Performance tab. Find the bottleneck. Is it mounting, re-rendering, or network? The answer changes your strategy.

If re-renders are the culprit, start with React.memo on pure presentational components. Then check if useCallback and useMemo actually help — they add overhead. Only use them when you pass props to memoized children or when a calculation costs more than a render.

For list-heavy UIs, virtualize. react-window or react-virtuoso render only what's visible. For large forms, isolate field state so one keystroke doesn't re-render the entire form. For images, lazy load and use modern formats like WebP.

Network optimization is worth more than micro-optimizations: code-split with React.lazy and Suspense, compress assets, cache API responses. The fastest render is the one you skip entirely.

ProfileFirst.tsxPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — interview tutorial

// BEFORE: Every search input keypress re-renders 200 rows
function SearchPage() {
  const [query, setQuery] = useState('');
  const results = useExpensiveSearch(query); // Runs every render
  return <ResultsList items={results} />;
}

// AFTER: Debounce + memoized component
function SearchPage() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);
  const results = useMemo(() => expensiveSearch(debouncedQuery), [debouncedQuery]);
  return <MemoizedResultsList items={results} />;
}
Output
Before: 40ms per keystroke. After: 4ms per keystroke.
💡Senior Shortcut:
Open React DevTools, check 'Highlight updates when components render.' If flashing on every interaction, you found your leak. Fix that before adding libraries.
🎯 Key Takeaway
Profile first, optimize second. The biggest gains come from reducing renders, not making renders faster.

React Server Components: The New Mental Model

React Server Components (RSC) represent a paradigm shift in how we build React applications. Unlike traditional client-side rendering where all components run in the browser, RSC allow components to execute on the server, sending only the resulting HTML to the client. This reduces bundle size and improves performance by offloading heavy computations and data fetching to the server.

Consider a blog post component that fetches data and renders markdown. In a client-side approach, you'd use useEffect to fetch data, which adds latency and a loading state. With RSC, the component runs on the server, fetches data directly (e.g., from a database), and renders the final HTML. The client receives a fully rendered page with no JavaScript for that component.

Example: A server component that fetches and renders a list of posts. It uses async/await directly, no hooks needed. The client never sees the fetch logic or loading state.

However, RSC cannot use state or effects. They are pure rendering functions that run once on the server. For interactivity, you combine them with client components (marked with 'use client'). This mental model requires thinking about where code executes: server for data fetching and static rendering, client for interactivity.

Key differences: Server components can access backend resources directly (database, file system) without exposing secrets. They reduce client-side JavaScript, improving load times. But they also introduce new patterns like streaming and suspense for loading states.

In production, RSC are ideal for content-heavy sites (blogs, e-commerce) where most UI is static or data-driven. They work seamlessly with Next.js App Router, but can be used standalone with React 19.

BlogPost.server.jsJSX
1
2
3
4
5
6
7
8
9
10
// Server Component (no 'use client')
export default async function BlogPost({ id }) {
  const post = await db.posts.findUnique({ where: { id } });
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}
Try it live
🔥Mental Model Shift
📊 Production Insight
Use RSC for static or data-fetching parts of your app (e.g., product listings, blog posts) to improve initial load time and SEO. Combine with client components for interactive features like forms or modals.
🎯 Key Takeaway
React Server Components run on the server, reducing client-side JavaScript and enabling direct backend access without exposing secrets.

React 19 Features: use(), Actions, Server Components

React 19 introduces several groundbreaking features that simplify data fetching and form handling. The new use() hook allows reading resources like promises or context directly in render, without wrapping in useEffect. For example, const data = use(fetchData()) suspends the component until the promise resolves, enabling a cleaner data-fetching pattern.

Actions are a new way to handle form submissions and mutations. Instead of manually managing loading states and error handling, Actions provide a declarative API. Example:

where submitAction is an async function. React automatically manages pending states, optimistic updates, and error boundaries.

Server Components (RSC) are now stable in React 19, allowing components to run exclusively on the server. Combined with Actions, you can build full-stack features without client-side JavaScript for data mutations.

Example: A form that creates a new user. With Actions, you define an async function that calls your API, and React handles the form state. The useActionState hook (formerly useFormState) provides pending and error states.

These features reduce boilerplate and improve performance by moving logic to the server. However, they require a new mental model: think in terms of server vs client boundaries.

In production, start using Actions for all forms and mutations. They integrate with Server Components for seamless full-stack experiences. The use() hook is ideal for data fetching in client components that need to suspend.

UserForm.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useActionState } from 'react';

async function createUser(prevState, formData) {
  const name = formData.get('name');
  // Simulate API call
  await new Promise(resolve => setTimeout(resolve, 1000));
  return { success: true, name };
}

export default function UserForm() {
  const [state, formAction, pending] = useActionState(createUser, null);
  return (
    <form action={formAction}>
      <input name="name" required />
      <button type="submit" disabled={pending}>
        {pending ? 'Creating...' : 'Create User'}
      </button>
      {state?.success && <p>User {state.name} created!</p>}
    </form>
  );
}
💡Use Actions for Forms
📊 Production Insight
Adopt Actions for all form submissions to get built-in loading states and error handling. Use use() for data fetching in client components that need suspense.
🎯 Key Takeaway
React 19's use() hook and Actions simplify data fetching and form handling, reducing boilerplate and improving developer experience.

State Management in 2026: useReducer, Zustand, TanStack Query

State management in React has evolved beyond Redux. In 2026, the go-to solutions are built-in hooks like useReducer, lightweight libraries like Zustand, and server-state tools like TanStack Query (formerly React Query).

useReducer is ideal for complex local state with multiple sub-values or when the next state depends on the previous one. Example: a shopping cart with add/remove items. It's simple, no external dependencies, and works well with Context for sharing state across components.

Zustand is a minimal state management library that provides a global store with a hook-like API. It's simpler than Redux, with no boilerplate, and supports middleware like persist. Example: a user preferences store that persists to localStorage.

TanStack Query handles server state (data from APIs) by managing caching, background refetching, and optimistic updates. It replaces most use cases for Redux or Context for async data. Example: fetching a list of products with automatic cache invalidation.

When to use what? Use useReducer for component-local complex state. Use Zustand for global client state (theme, auth). Use TanStack Query for server state (API data). Avoid Redux unless you have very complex global state logic or need middleware.

In production, a typical stack: TanStack Query for data fetching, Zustand for UI state, and useReducer for form state. This combination is lightweight, performant, and easy to maintain.

store.jsJS
1
2
3
4
5
6
7
8
9
10
11
12
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useStore = create(
  persist(
    (set) => ({
      theme: 'light',
      toggleTheme: () => set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })),
    }),
    { name: 'theme-storage' }
  )
);
⚠ Avoid Over-Engineering
📊 Production Insight
Adopt TanStack Query for all API interactions to get caching and refetching out of the box. Use Zustand for UI state like modals or themes. Keep Redux only for legacy projects.
🎯 Key Takeaway
Modern state management uses useReducer for local state, Zustand for global client state, and TanStack Query for server state, replacing Redux in most cases.
● Production incidentPOST-MORTEMseverity: high

List Reordering Broke Input Focus: The Hidden Key Bug

Symptom
When prepending an item to a list, the text cursor jumped to an unexpected input field. React preserved the component's state across re-renders but associated it with the wrong array element because the key (index) didn't uniquely identify the item.
Assumption
The team assumed any unique value, including index, would work fine for list keys since all items were distinct in content.
Root cause
Using array index as key tells React the first element is always the same component instance. When new item is inserted at index 0, React reuses the first component instance (which had the input state) and assigns it to the new data, causing the old input state to appear on the wrong element.
Fix
Replace key={index} with key={item.id} or another stable unique identifier. React then correctly associates component instances with data items regardless of position.
Key lesson
  • Never use array index as key if the list order can change (insert, delete, sort).
  • Stable ID-based keys preserve component state across re-renders.
  • React's reconciliation relies on keys to match previous component instances to current data.
Production debug guideQuick symptom-to-fix guide for the glitches that trip up mid-level devs4 entries
Symptom · 01
Component state resets unexpectedly on list reorder
Fix
Check that each list item has a stable key (unique ID). Swap key={index} to key={item.id} and verify re-renders preserve state.
Symptom · 02
useEffect runs more often than expected or misses dependencies
Fix
Review the dependency array. Use ESLint plugin react-hooks/exhaustive-deps. If an empty array is intended, double-check that no external values are used inside the effect.
Symptom · 03
Child component doesn't re-render after parent state change
Fix
Verify the child doesn't have React.memo incorrectly applied. Check that the prop passed is a new reference on each render (avoid inline objects/arrays). Use useMemo/useCallback if needed.
Symptom · 04
Form inputs don't update when typing
Fix
Check for controlled component pattern: value={state} onChange={(e) => setState(e.target.value)}. If using uncontrolled, ref might not trigger re-render.
★ Quick Debug Cheat Sheet — React Interview TopicsThree common React interview-related production bugs and the exact command or fix to apply.
List input states get mixed when items reorder
Immediate action
Stop typing and check list key prop usage.
Commands
console.log('key:', key) in render to see current keys
git grep 'key={index}' to find all index-based keys
Fix now
Replace key={index} with key={item.id} (or another stable unique value).
useEffect runs every render — infinite loop+
Immediate action
Check second argument to useEffect.
Commands
console.log('deps:', deps) to log dependency array each render
Check if state setter is inside the effect without deps
Fix now
Add proper dependency array: useEffect(() => {...}, [dep1, dep2]).
React.memo wrapped component still re-renders on parent update+
Immediate action
Verify props are referentially stable.
Commands
Use React DevTools Profiler to see why component re-rendered
console.log('props changed', newProps, prevProps) in custom comparison
Fix now
Wrap callback props with useCallback and objects/arrays with useMemo.
Feature Comparison
FeatureVirtual DOMReal DOM
Update SpeedBlazing fast (JavaScript objects)Slow (triggers browser layout/reflow)
Memory UsageLow (only in-memory objects)High (complex browser structures)
EfficiencyBatched updates via ReconciliationIndividual, manual manipulation
ConsistencyDeclarative (UI follows State)Imperative (UI must be manually poked)
⚙ Quick Reference
13 commands from this guide
FileCommand / CodePurpose
iothecodeforgereactReconciliationDemo.jsx/**The Virtual DOM and Reconciliation
iothecodeforgereactHookRulesViolation.jsx/**Hooks Rules
iothecodeforgereactControlledVsUncontrolled.jsx/**Controlled vs Uncontrolled Components
iothecodeforgereactuseEffectCleanup.jsx/**useEffect and Lifecycle
iothecodeforgereactPerformanceOptimisation.jsx/**Performance Optimisation
ReconciliationPitfall.pyfunction OrderList({ orders }) {How Does React Actually Work? (The Bits That Matter in Produ
JSXCompilation.pyfunction Greeting({ user }) {JSX
ClassVsFunction.pyclass UserProfile extends React.Component {Components
WhenContextHurts.tsxconst CartContext = React.createContext();When should you use Redux over Context API?
ProfileFirst.tsxfunction SearchPage() {How would you optimize a slow React application?
BlogPost.server.jsexport default async function BlogPost({ id }) {React Server Components
UserForm.jsxasync function createUser(prevState, formData) {React 19 Features
store.jsexport const useStore = create(State Management in 2026

Key takeaways

1
React's efficiency comes from the Virtual DOM, which avoids expensive browser reflows by batching updates.
2
Reconciliation is the process where React diffs two virtual trees to determine the minimum number of changes needed for the Real DOM.
3
Keys are not just for suppressing console warnings; they are essential for React to track item identity across re-renders.
4
Answering 'Why' (design decisions) is more valuable than answering 'How' (syntax) in a mid-to-senior level interview.
5
Hooks rely on call order
conditional calls break React's internal linked list of hook states.
6
Memoisation (React.memo, useMemo, useCallback) should be added after profiling, not preemptively.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the virtual DOM and how does it differ from the real DOM?
Q02SENIOR
Explain the Rules of Hooks. Why does React enforce calling hooks only at...
Q03SENIOR
What is the difference between controlled and uncontrolled components? W...
Q04SENIOR
How does React.memo differ from useMemo and useCallback?
Q05SENIOR
What happens if you use an empty dependency array in useEffect but acces...
Q01 of 05JUNIOR

What is the virtual DOM and how does it differ from the real DOM?

ANSWER
The virtual DOM is a lightweight JavaScript object representation of the real DOM. React uses it to minimize direct manipulation of the browser DOM, which is expensive. When state changes, React creates a new virtual tree, diffs it with the previous one (reconciliation), and then applies only the minimal set of changes to the real DOM. This makes updates faster and more predictable.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between a Controlled and Uncontrolled component?
02
How does React's 'useEffect' handle component lifecycle events?
03
What is 'lifting state up' in React?
04
What is the purpose of the key prop in lists?
05
When should you use useReducer instead of useState?
N
Naren Founder & Principal Engineer

20+ years shipping production code across the stack, with years spent interviewing engineers. Drawn from code that ran under real load.

Follow
Verified
production tested
July 19, 2026
last updated
2,466
articles · all by Naren
🔥

That's JavaScript Interview. Mark it forged?

9 min read · try the examples if you haven't

Previous
JavaScript Closures Interview Q
3 / 8 · JavaScript Interview
Next
Node.js Interview Questions