React · React Fundamentals

React Fundamentals Interview Questions
With Answers & Code Examples

18 carefully curated React Fundamentals interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
18 questions6 beginner4 core8 advanced
Q1Beginner

What is React and what problem does it solve?

💡 Hint: Declarative UI library — describe what the UI should look like, not how to update it

React is a JavaScript library for building user interfaces. It solves the problem of keeping the UI in sync with application state.

Core ideas:

  • Declarative — you describe the desired UI for a given state; React figures out DOM updates
  • Component-based — split UI into reusable, self-contained pieces
  • Virtual DOM — React maintains a lightweight copy of the DOM, diffs it on state change, and batches minimal real DOM updates
  • Unidirectional data flow — data flows down (props), events flow up (callbacks)

// Imperative (jQuery style) — how to update
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
  const count = parseInt(btn.textContent) + 1;
  btn.textContent = count;
});

// Declarative (React style) — what it should look like
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
💡 React's real innovation was making UI a pure function of state: UI = f(state). Any time state changes, React re-runs the function and reconciles the difference.
Practice this question →
Q2Beginner

What is JSX and how does it work under the hood?

💡 Hint: JSX is syntactic sugar — Babel transforms it into React.createElement() calls

JSX is a syntax extension that looks like HTML inside JavaScript. It is NOT valid JS — it gets compiled by Babel into React.createElement() calls.

// What you write
const el = 

Hello {name}

; // What Babel compiles it to const el = React.createElement('h1', { className: 'title' }, 'Hello ', name); // React 17+ — new JSX transform (no need to import React) import { jsx as _jsx } from 'react/jsx-runtime'; const el = _jsx('h1', { className: 'title', children: ['Hello ', name] });

Key rules:

  • Single root element required (use <></> Fragment if needed)
  • Use className not class, htmlFor not for
  • Expressions in {} — not statements (if, for)
  • Self-close empty tags: <img />, <br />
  • camelCase for attributes: onClick, onChange
💡 JSX returns plain JS objects (React elements). They're just descriptions — cheap to create. React uses these to build and diff the Virtual DOM.
Practice this question →
Q3Beginner

What is the Virtual DOM and how does reconciliation work?

💡 Hint: Lightweight JS copy of the DOM — React diffs old vs new tree, applies minimal patches

The Virtual DOM is a JavaScript object tree that mirrors the real DOM. It's cheap to create and manipulate.

Reconciliation process:

  1. State/props change triggers a re-render — React calls your component function
  2. React builds a new Virtual DOM tree
  3. Diffing — React compares (diffs) new tree with the previous tree
  4. Committing — React applies only the changed nodes to the real DOM

Diffing heuristics (O(n) instead of O(n³)):

  • Elements of different types → tear down and rebuild the whole subtree
  • Same type → update only changed attributes
  • Lists → use key prop to match old and new items
// React sees these as DIFFERENT types → full rebuild
...
... // destroys div, creates span // Same type → just updates className attribute
💡 Reconciliation is why key matters so much in lists. Without a stable key, React might reuse the wrong DOM node, causing subtle bugs like input state being wrong item.
Practice this question →
Q4Beginner

What is the difference between a controlled and uncontrolled component?

💡 Hint: Controlled = React owns the value; uncontrolled = DOM owns it (accessed via ref)

Controlled component — React state is the single source of truth. Every change goes through setState.

Uncontrolled component — the DOM stores the value. You pull it with a ref when needed.

// Controlled — React owns the value
function Controlled() {
  const [value, setValue] = useState('');
  return (
     setValue(e.target.value)}
    />
  );
}

// Uncontrolled — DOM owns the value
function Uncontrolled() {
  const inputRef = useRef(null);
  function handleSubmit() {
    console.log(inputRef.current.value); // pull on demand
  }
  return ;
}

When to use each:

  • Controlled — instant validation, conditional disabling, dynamic inputs, format-on-type
  • Uncontrolled — file inputs (always uncontrolled), integrating with non-React code, simple one-off forms where you only need value on submit
💡 React recommends controlled components for most cases. File inputs (<input type="file">) are always uncontrolled — you can't set their value programmatically for security reasons.
Practice this question →
Q5Beginner

What are props and how is prop drilling a problem?

💡 Hint: Props pass data down; drilling = passing through many layers just to reach a deeply nested child

Props are the inputs to a component — passed from parent to child. They are read-only (immutable from the child's perspective).

function Button({ label, onClick, disabled = false }) {
  return ;
}

// Usage

Prop drilling — passing props through intermediate components that don't need them, just so a deep child can access them:

// ❌ Drilling — Middle doesn't need user, just passes it down
function App() {
  const user = { name: 'Alice' };
  return ;
}
function Middle({ user }) {
  return ;
}
function Deep({ user }) {
  return 

{user.name}

; }

Solutions to prop drilling:

  • Context API — broadcast value to any descendant
  • Component composition — lift rendering up, pass JSX as children or slots
  • State management — Zustand, Redux, Jotai for global state
💡 Before reaching for Context, try composition first. Pass children or render props — often eliminates drilling without the overhead of a context.
Practice this question →
Q6Core

What is the key prop and why is it critical in lists?

💡 Hint: Key lets React identify which item changed, was added, or removed — must be stable and unique

The key prop is React's mechanism to identify list items across renders. Without stable keys, React can't efficiently reconcile list changes.

// ❌ Using index as key — problematic when list reorders or items are inserted
{items.map((item, index) => (
  
))}

// ✅ Use a stable unique ID from the data
{items.map(item => (
  
))}

Why index keys fail:

// Initial:  [A(key=0), B(key=1), C(key=2)]
// Delete A: [B(key=0), C(key=1)]
// React sees key=0 CHANGED (not deleted) → updates B in place
// Key=2 REMOVED → destroys C
// Result: input state, focus, animations on wrong elements

When index keys are safe:

  • The list never reorders
  • Items are never inserted or deleted from the middle
  • Items have no state (text-only renders)
💡 Keys must be unique among siblings, not globally. Keys don't get passed as props — if you need the ID in the component, pass it explicitly as a separate prop.
Practice this question →
Q7Core

What are React Fragments and when do you need them?

💡 Hint: <></> lets you return multiple elements without adding an extra DOM node

Fragments let you group multiple elements without adding an extra DOM wrapper node.

// ❌ Extra div pollutes the DOM
function TableRow() {
  return (
    
{/* invalid inside */} Name Age
); } // ✅ Fragment — no DOM node added function TableRow() { return ( <> Name Age ); } // Keyed fragments — use the long form syntax (shorthand doesn't support key) function List({ items }) { return items.map(item => (
{item.term}
{item.description}
)); }
💡 Fragments matter most in table structures (tr/td/th must be direct children) and flexbox/grid layouts where an extra wrapper breaks the CSS.
Practice this question →
Q8Core

What is the component lifecycle and how do hooks map to it?

💡 Hint: Mount → Update → Unmount — useEffect covers all three phases

Class components had explicit lifecycle methods. Hooks cover the same phases:

Class methodHook equivalent
componentDidMountuseEffect(() => { ... }, [])
componentDidUpdateuseEffect(() => { ... }, [deps])
componentWillUnmountuseEffect(() => { return () => cleanup() }, [])
shouldComponentUpdateReact.memo + useMemo
getDerivedStateFromPropsCompute during render (no hook needed)
getSnapshotBeforeUpdateuseLayoutEffect return value (rare)
useEffect(() => {
  // componentDidMount — runs after first render
  const sub = subscribe(userId);

  // componentWillUnmount — cleanup function
  return () => sub.unsubscribe();
}, []); // [] = run once

useEffect(() => {
  // componentDidUpdate for userId — runs when userId changes
  fetchUser(userId);
}, [userId]);
💡 useEffect runs AFTER the browser paints. For DOM measurements that need to run synchronously before paint (to avoid flicker), use useLayoutEffect instead.
Practice this question →
Q9Beginner

What is the difference between state and props?

💡 Hint: Props = external inputs (immutable); state = internal memory (mutable via setState)
  • Props — passed in from outside, owned by the parent, read-only inside the component
  • State — owned and managed by the component itself, mutable via setState/useState, triggers re-render when changed
function Counter({ initialCount, step = 1 }) {
  //              ↑ props — given from parent, read-only here

  const [count, setCount] = useState(initialCount);
  //     ↑ state — owned by this component

  return (
    
  );
}

When state changes: React re-renders the component and all its children.

When props change: the parent re-renders, passing new props, which causes the child to re-render.

💡 A common mistake: trying to sync state with props using useEffect. Instead, derive values during render if possible. If a prop controls the initial value, use it only in useState's initializer — not as a useEffect dep.
Practice this question →
Q10Advanced

What is StrictMode and what does it do in development?

💡 Hint: Intentionally double-invokes render, effects to expose impure code and side effects — dev only, no production cost

React.StrictMode is a development tool that helps you find bugs by intentionally stressing your components.

What it does (dev only):

  • Double-invokes render functions — component function called twice to detect impure renders
  • Double-invokes effects — mounts, unmounts, and remounts every component to surface missing cleanup
  • Warns about deprecated APIs — componentWillMount, findDOMNode, etc.
// StrictMode reveals this bug:
function BadComponent() {
  const items = []; // 💥 rendered twice, items reset each time — detected!
  items.push('item'); // pure render must not have side effects
  return 
{items}
; } // StrictMode reveals missing cleanup: useEffect(() => { const ws = new WebSocket(url); // mount #1 — created // StrictMode: unmount → cleanup should close ws // mount #2 — second connection opened // If no cleanup, you'd have two connections — StrictMode exposes this return () => ws.close(); // ✅ proper cleanup }, [url]); // Usage root.render( );
💡 In React 18, StrictMode's double-effect firing is intentional preparation for a future feature (Offscreen). If your effect fires twice and causes a bug, you have a missing cleanup function — fix that, don't remove StrictMode.
Practice this question →
Q11Advanced

Understanding Interruptible Rendering in React Fiber

💡 Hint: Consider how React Fiber's data structure and reconciliation algorithm enable the framework to handle rendering interruptions and prioritize tasks

Interruptible rendering is a key feature of React Fiber, allowing the framework to pause and resume rendering as needed. This is achieved through the use of a scheduler, which prioritizes tasks and handles context switching between them. The Fiber data structure is used to represent the component tree, and the reconciliation algorithm determines what changes need to be applied to the DOM. Interruptible rendering enables features like time slicing and concurrent mode, which improve the overall user experience by reducing the time spent on rendering and allowing for more efficient handling of user interactions.

class ExampleComponent extends React.Component {
render() {
return (<>Your JSX here);
}
}

In terms of implementation, the developer can utilize the React.startTransition API to wrap parts of the component tree that should be updated without interrupting the current render. This ensures a seamless user experience even in the face of complex, computationally expensive updates.

Practice this question →
Q12Advanced

Understanding Reconciliation Diffing in React

💡 Hint: Think about how React could efficiently update the UI without re-rendering the entire component tree on every state change

Reconciliation diffing is a process in React that helps to efficiently update the UI by comparing the new and old Virtual DOM representations. This process is crucial for optimizing the rendering of components and preventing unnecessary re-renders.

The key steps involved in reconciliation diffing include:

1. Creating a new Virtual DOM representation based on the updated state and props.
2. Comparing the new Virtual DOM with the previous one to identify the differences.
3. Updating the actual DOM by applying the minimum number of changes required to reflect the updated Virtual DOM.

There are two main types of reconciliation diffing in React:

1. Tree reconciliation: This involves comparing the Virtual DOM tree with the previous one and updating the actual DOM accordingly.
2. Component reconciliation: This involves comparing the props and state of individual components and updating them only when necessary.
Practice this question →
Q13Core

Optimizing Component Performance with useMemo

💡 Hint: Think about how you can prevent a function from being called repeatedly when its inputs haven't changed, and how this relates to React's rendering cycle

The useMemo hook in React is used to memoize (remember) the result of a function so that it's not recalculated every time the component re-renders. This can be particularly useful for optimizing performance when dealing with expensive function calls.

import { useMemo } from 'react'; function Example() { const calculate = (numbers) => { let sum = 0; numbers.forEach((number) => { sum += number; }); return sum; }; const numbers = [1, 2, 3, 4, 5]; const total = useMemo(() => calculate(numbers), [numbers]); return ( 
The sum of numbers is: {total}
); }

In this example, useMemo ensures that the calculate function is only called when the numbers array changes, thus optimizing component performance by avoiding redundant calculations.

Practice this question →
Q14Advanced

Understanding React Rendering Process

💡 Hint: Think about what happens when the state of a React component changes and how React decides what to update in the DOM

The React rendering process involves several key steps. When the state of a component changes, React creates a new virtual DOM representation of the component tree. This new virtual DOM is then compared to the previous virtual DOM to determine the minimum number of changes needed to update the actual DOM. This process is known as reconciliation.

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

render() {
return (

Count: {this.state.count}




);
}
}

In the above example, when the button is clicked, the state of the component changes, triggering a re-render. React will then create a new virtual DOM and compare it to the previous one to determine the changes needed to update the actual DOM. This process happens efficiently and automatically, allowing developers to focus on writing the application logic without worrying about the low-level details of DOM updates.

Practice this question →
Q15Advanced

Understanding flushSync in React

💡 Hint: Consider the implications of using a synchronous update method in a framework that is designed to be asynchronous

flushSync is a method in React that allows you to force the execution of pending updates in the React state.

When you call flushSync, React will synchronously update the DOM with the latest state changes. This can be useful when you need to ensure that the UI is updated immediately after a state change.

import { flushSync } from 'react-dom';

function App() {
  const [count, setCount] = useState(0);

  return (
    

Count: {count}

); }
Practice this question →
Q16Advanced

Understanding React Fiber Architecture

💡 Hint: Think about how React can optimize the reconciliation process to prevent the main thread from being blocked for too long, and how this relates to the concept of 'fibers'

The React Fiber Architecture is a core concept in React that allows for efficient and incremental updates to the virtual DOM. It was introduced in React 16 as a replacement for the traditional stack-based reconciliation algorithm. The main idea behind fibers is to break down the reconciliation process into smaller, manageable units, called 'fibers', which can be paused and resumed as needed. This allows React to yield control back to the browser at regular intervals, preventing the main thread from being blocked for too long.

class Fiber {
constructor(tag, key, type, props) {
this.tag = tag;
this.key = key;
this.type = type;
this.props = props;
this.stateNode = null;
this.child = null;
this.sibling = null;
this.return = null;
this.index = 0;
this.ref = null;
this.pendingProps = null;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.contextDependencies = null;
}

Benefits of Fiber Architecture:

  • Improved performance by reducing the time spent on reconciliation
  • Ability to pause and resume reconciliation, allowing for better handling of interruptions and improved responsiveness
  • Support for concurrent rendering and other advanced features
Practice this question →
Q17Advanced

Understanding React 18's Automatic Batching

💡 Hint: Consider how React's state update handling has changed between versions 17 and 18, specifically how re-renders are triggered

React 18 introduced a new feature called Automatic Batching, which automatically batches multiple state updates into a single re-render. This feature improves the performance of React applications by reducing the number of re-renders.

import { useState } from 'react'; function Counter() {   const [count, setCount] = useState(0);   function handleClick() {     setCount(count + 1);     setCount(count + 1);   }   return (     

Count: {count}

); }

In React 17 and earlier, the above code would cause the component to re-render twice. However, with React 18's Automatic Batching, the two state updates are batched into a single re-render.

Key differences: The key difference between React 17 and React 18 is how they handle state updates. In React 17, each state update triggers a re-render, whereas in React 18, multiple state updates are batched into a single re-render.

Practice this question →
Q18Advanced

Optimizing Batch Rendering in React

💡 Hint: Look into React's unstable_batchedUpdates method and how it can be used to improve performance

Batch rendering in React refers to the process of grouping multiple state updates together and applying them in a single render cycle. This approach helps to improve performance by reducing the number of unnecessary re-renders. To optimize batch rendering, you can use the ReactDOM.flushSync method to ensure that all updates are applied synchronously, or use the ReactDOM.unstable_batchedUpdates method to batch updates together.

import { unstable_batchedUpdates } from 'react-dom'; unstable_batchedUpdates(() => {  setState1();  setState2(); });

Alternatively, you can use the useCallback or useMemo hooks to memoize functions or values and prevent unnecessary re-renders.

Practice this question →

Other React Interview Topics

Rendering StrategiesCore JSType SystemFunctionsMicrofrontendsGenericsAsync JSHooksObjectsMonorepoArrays'this' KeywordUtility TypesError HandlingModern JSBundle OptimizationPerformanceDOM & EventsState ManagementClasses & OOPCaching StrategiesComponent PatternsAdvanced TypesAuthenticationReact RouterFormsAdvanced PatternsFrontend SecurityConcurrent ReactServer ComponentsTestingEcosystemNetwork OptimizationCore Web VitalsBrowser APIs

Ready to practice React Fundamentals?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →