Lessons available in both languages
MERN Stack · Interview Prep

React interview questions & answers

222+ real React interview questions with model answers, plus free lessons to learn the concepts. Prepare in English & Hinglish, then practise with an AI mock interview.

21 topics · 222+ questions

How Hirenix teaches

One chapter. 90 minutes.
Interview-ready.

Every concept starts with a real-world problem — the kind that actually shows up in production code. Nothing to cram; it just clicks. Every question comes with a model answer: exactly what to say in the room, and why. Then an AI mock interview on the same chapter.

  • 📖Concept in 5 minutesNo jargon — straight to the point
  • 🛠️Real-world problemThe kind production code throws at you
  • 💬Model answerExactly what to say in the room
  • 🧠FlashcardsRevise in 10 minutes
  • 🤖AI mock interviewIt asks follow-ups too
  • 📊Weak topicsSee exactly where you're stuck
Start this chapter — free🌐 English🇮🇳 Hinglish
A student learning an interview concept on Hirenix at home
Video playlistbuilt around a syllabus18h+
Hirenix chapterbuilt around interviews90 min

The difference isn’t the content — it’s the filter. Only what’s actually used in production and actually asked in interviews. Textbook topics the industry never touches don’t make the cut.

Lessons available in both languages

What you’ll learn

  • What is React?
  • What is JSX?
  • Components & PropsFree account
  • State & useStateFree account
  • useEffect & lifecycleFree account
  • useRef & forwardRefFree account
  • Conditional renderingFree account
  • Lists & keysFree account
  • Forms & inputsFree account
  • Context APIFree account
  • useReducerFree account
  • Custom hooksFree account
  • Redux basicsFree account
  • Data fetching & React QueryFree account
  • Performance & memoFree account
  • Error boundaries & SuspenseFree account
  • React RouterFree account
  • Interview recapFree account
  • Project 1: Counter
  • Project 2: Signup form + validationFree account
  • Project 3: Todo app (full build)Free account

What is React?

Imagine building a house with a Lego set. You do not build the whole house at once — you snap together small blocks. If one block breaks, you swap just that block, not the whole house.

React works exactly like this. It breaks a website into small "components" (blocks) — Navbar, Button, Card. Each is reusable, and if one changes, only that piece updates — the whole page does not reload.

🌍 Real-world example: On the Instagram feed, like one post and only that post's heart turns red; the whole feed does not refresh. That is React: only what changed is drawn again (a "re-render").

The problem React was built to solve

Before React, updating a page meant reaching into the page yourself and rewriting bits of it by hand:

document.getElementById("count").textContent = count;
document.getElementById("badge").classList.toggle("hidden", count === 0);

Every new feature added another line like that, and every one of them had to be kept in sync with the data by hand. Miss one and the screen quietly disagrees with your data — the classic "the number changed but the badge didn't" bug.

Imperative means you write the steps: find the element, change this property, toggle that class. Declarative means you describe the result and let the library work out the steps:

<span>{count}</span>
{count > 0 && <Badge />}

You never say "now go update the span". You say what the screen should look like for this data, and React makes the screen match. That single shift is the whole point of React — and it is the answer interviewers are listening for.

💡 Jargon check: "the DOM" is the browser's live object version of your page — the tree of elements JavaScript can read and change. "Manipulating the DOM" just means changing what is on screen from code.

The Virtual DOM — and what it actually buys you

Touching the real DOM is comparatively slow, and React re-runs your component on every state change. So React keeps a virtual DOM: a lightweight JavaScript description of what the UI should look like. On a change it builds a new description, diffs it against the previous one (this step is called reconciliation), and applies only the differences to the real DOM.

Be careful how you say this in an interview. The virtual DOM is not magic and is not automatically faster than hand-written DOM code — a careful developer updating exactly one node by hand will beat it. What it buys you is that you stop hand-writing that code at all: you get near-optimal updates by default, without tracking which node needs which change. Predictability, not raw speed, is the win.

Library, not framework

React is a library — it renders UI, and that is it. It ships no router, no data-fetching layer, no form validation, no opinion about your folder structure. You assemble those yourself (React Router, TanStack Query, and so on). A framework like Angular or Next.js hands you those decisions already made.

That trade is real and cuts both ways: React gives you freedom and gives you the bill for it — more decisions, more assembly, and no single official answer to "how should I do X?"

SPA vs MPA — a common senior question

React apps are usually SPAs (Single Page Applications): the browser loads one HTML shell plus your JavaScript, and every later "page change" is JavaScript swapping components — no full page reload. An MPA (Multi Page Application) asks the server for a fresh HTML document on every navigation.

SPA (typical React) MPA (traditional)
Navigation instant, no reload full page request
First load heavier — JS must arrive first lighter — HTML arrives ready
SEO needs care (SSR / a framework) works out of the box
Feels like an app a website

When to use React: the screen changes a lot in response to the user — dashboards, feeds, chat, editors, multi-step forms, anything where state moves around and many pieces of UI must stay in sync with it. Also when you want to reuse the same UI piece in many places.

When NOT to use it: a blog, a landing page, a docs site, or a mostly-static marketing page. Shipping a JavaScript runtime so the browser can render text that could have been plain HTML makes the first load slower and SEO harder for no gain. Plain HTML or a static-site generator wins there. Two more honest limits: React does not make your app fast for free (its own defaults re-render more than you might expect), and it is a big ecosystem to learn on top of JavaScript.

Your first app

The common way to scaffold one today:

npm create vite@latest my-app -- --template react
cd my-app && npm install && npm run dev

You may still see create-react-app in older tutorials — it is deprecated and no longer recommended. For a full application, React's own docs point you at a framework (such as Next.js or React Router) rather than a bare setup.

Confusions worth clearing early

  • React is not a language. It is JavaScript. Weak JavaScript is the real reason most React feels hard.
  • React is not full-stack. It runs in the browser and knows nothing about your database.
  • React vs React Native: same idea and same mental model, different render target — DOM vs native mobile views.
  • "Component" is just a function that returns UI. Nothing more mystical than that.

Standard definition: React is an open-source JavaScript library developed by Meta for building user interfaces using reusable, component-based architecture with a virtual DOM for efficient rendering.

function Welcome() {
  return <h1>Hello, Hirenix!</h1>;
}
// Use it on screen:
<Welcome />

What is JSX?

Think of JSX like writing a WhatsApp message with emojis mixed into text — the emojis aren't plain text, but your phone understands and shows them. JSX mixes HTML-looking tags right inside JavaScript so your UI reads naturally.

But the browser only understands plain JavaScript, not JSX. So a compiler (Babel, or SWC/esbuild depending on the toolchain) quietly translates your JSX into plain JS function calls before the browser ever sees it — since React 17 the default automatic transform emits _jsx(...) calls from react/jsx-runtime; the older classic transform emitted React.createElement(...).

💡 What is Babel? Babel is a free tool called a compiler — it converts code the browser can't read (like JSX, or the newest JavaScript features) into old, plain JavaScript that every browser understands. It runs automatically when your project builds, like a translator working in the background.

🌍 Real-world example: You write <h1>Hello</h1> inside a JS file. With the modern JSX runtime this compiles to _jsx('h1', { children: 'Hello' }), which produces a plain JS object React can render (the older classic transform produced the same object via React.createElement('h1', null, 'Hello')). You write easy HTML-like code; React gets the objects it needs.

The rules JSX will not let you break

1. Return one parent element. A function can return one value, and JSX compiles to one function call. Two siblings at the top level is a syntax error. Wrap them — in a real element, or in a Fragment (<>...</>) when you do not want an extra <div> in the DOM.

2. className, not class. class is a reserved word in JavaScript. Same reason for on a label becomes htmlFor.

3. camelCase for props and events. onclickonClick, tabindextabIndex, background-colorbackgroundColor.

4. Every tag closes. <img> and <br> are legal HTML but illegal JSX — write <img />, <br />.

5. {} is an escape hatch back into JavaScript. Anything inside braces is an expression — it must produce a value. {user.name}, {2 + 2}, {items.length > 0 && <List />} all work. An if statement does not, because a statement produces no value.

6. Style is an object, not a string. style={{ color: "red" }} — the outer braces enter JavaScript, the inner ones are the object.

Conditional rendering inside JSX

Because {} takes an expression, you get three idioms:

{isLoggedIn && <Dashboard />}                   // render, or render nothing
{isLoggedIn ? <Dashboard /> : <Login />}        // one or the other
{status === "loading" && <Spinner />}

⚠️ The && trap that ships to production. {count && <Badge />} renders a literal 0 on screen when count is 0, because 0 is falsy and React renders the number 0 (unlike false, null and undefined, which render nothing). Write {count > 0 && <Badge />}.

When to use which: && when the alternative is nothing. A ternary when there are genuinely two outcomes. Neither when the branches get long — pull the decision above the return into a variable or an early return, because nested ternaries inside JSX are the single most common readability complaint in code review.

Children, spread and comments

<Card>Anything here arrives as props.children</Card>

const props = { id: 1, title: "Hi" };
<Post {...props} />          {/* spread — same as id={1} title="Hi" */}

{/* this is a JSX comment */}

Spread is convenient and easy to overuse: <Post {...props} /> hides which props the component actually receives, so a typo or a stale key stays invisible. Prefer naming props explicitly when the list is short.

What JSX is not

JSX is not HTML and it is not a template language. It is syntax sugar over function calls, which is why normal JavaScript rules apply inside it — .map() works, variables work, and you can assign JSX to a variable or return it from a function.

const el = <h1>Hi</h1>;      // just an object
console.log(el.type);         // "h1"

Trade-off: JSX buys readability at the cost of a build step — your code no longer runs in a browser as-is, and the thing you are looking at is an object, not markup, which is why HTML habits (class, unclosed tags, string styles) break. That indirection is also why a JSX error message often points at the compiled output rather than the line you wrote.

Is JSX mandatory? No. You can call _jsx/createElement by hand. Nobody does, because the nested calls become unreadable fast — but knowing it is optional is exactly what an interviewer is checking when they ask "what is JSX really?".

Mistakes that come up in interviews

  • Using class instead of className
  • Writing an if statement inside {} instead of a ternary or &&
  • {count && ...} printing a stray 0
  • Forgetting to close <img> / <input>
  • style={{color: 'red'}} written as style="color: red"

Standard definition: JSX (JavaScript XML) is a syntax extension for JavaScript that lets you write HTML-like markup inside JavaScript; a compiler turns it into plain JS function calls — _jsx/jsxs calls from react/jsx-runtime in the modern automatic transform, or React.createElement calls in the older classic transform — that return React elements.

const name = "Hirenix";
const heading = <h1>Hello, {name}!</h1>;
// With the modern JSX runtime this compiles to:
// _jsxs('h1', { children: ['Hello, ', name, '!'] })   // from react/jsx-runtime
// (the older classic transform emitted React.createElement('h1', null, 'Hello, ', name, '!'))

Project 1: Counter

What we're building: A Counter app — a number on the screen with +, , and Reset buttons. It's tiny, but it teaches the single most important loop in all of React: event → setState → React re-runs your component → re-render → UI updates. Master this and everything else is just variations of it.

React concepts it teaches: useState, event handlers (onClick), the setter function, functional updates (setCount(c => c + 1)) vs value updates (setCount(count + 1)), and the golden rule — you change STATE, not the DOM.


Step 0 — The mental model (read this first, 30 seconds)

In plain HTML/JavaScript, if you wanted to change a number on screen you'd grab the element and write to it: document.getElementById('x').innerText = 5. You touch the DOM by hand.

React works backwards. You never touch the DOM. Instead you keep a piece of data called state, and you tell React what the screen should look like for a given state. When state changes, React redraws the screen for you. Your only job is: when something happens, update the state. React does the drawing.

💡 State = a value React watches. When it changes, React re-draws the component that uses it.

Keep that picture in your head. Now let's build.


Step 1 — Just show a number

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
    </div>
  );
}

export default Counter;

What's happening: useState(0) creates one piece of state and starts it at 0. It hands you back an array with exactly two things, and we unpack them with const [count, setCount] = ...:

  • count — the current value (0 right now). This is a read-only snapshot.
  • setCount — the setter, the ONLY correct way to change that value.

Inside the JSX, {count} drops the current value into the page, so the screen shows Count: 0. Notice: we didn't touch the DOM, we just described what to show for the current state. That's the whole idea.

💡 Why not just let count = 0? Because a normal variable, when you change it, doesn't tell React anything — the screen would never update. useState gives React a value it watches.


Step 2 — The + button (this is the big one)

import { useState } from 'react';

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

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

export default Counter;

What's happening: This is the core loop — trace it slowly, because every button in every React app works exactly this way:

  1. You click the + button.
  2. That fires the button's onClick, which runs the arrow function () => setCount(count + 1).
  3. count is 0 right now, so this calls setCount(0 + 1)setCount(1).
  4. The setter tells React: "hey, this state's new value is 1."
  5. React responds by re-running the whole Counter function from the top.
  6. On this new run, useState gives back the updated value — now count is 1.
  7. The JSX produces Count: 1, React compares it to what's on screen and re-renders just the changed part.
  8. You see Count: 1.

💡 onClick is React's way to attach a click handler. We give it a function (() => ...), not a call. If you wrote onClick={setCount(count + 1)} (no arrow), it would run immediately during render, not on click — a classic beginner bug.

The button never touched the number on screen. It changed state. State changing is what caused React to redraw. Say it out loud: the button changes state, not the DOM.


Step 3 — The − button (decrement)

<p>Count: {count}</p>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count + 1)}>+</button>

What's happening: Exact same loop, opposite math. Click onClick runs setCount(count - 1). If count is 3, that's setCount(2). Setter tells React state changed → React re-runs Countercount is now 2 → screen shows Count: 2. Two buttons, one shared state — both just call the setter with a different number.


Step 4 — The Reset button

<button onClick={() => setCount(0)}>Reset</button>

What's happening: Reset is the simplest of all — it doesn't care what count currently is. It calls setCount(0), hard-setting state back to 0. Setter → React re-runs Countercount is 0 → screen shows Count: 0. Same loop again. Once you see the loop, you can add any button you want.


Step 5 — Functional update: setCount(c => c + 1)

Swap the + handler from a value update to a functional update:

<button onClick={() => setCount(c => c + 1)}>+</button>

What's happening: Both versions add 1, so why bother? The difference is where the +1 starts from.

  • setCount(count + 1) — a value update. It uses count as captured at the moment this click handler was created. That snapshot can be stale.
  • setCount(c => c + 1) — a functional update. You pass a function. React calls it and hands it the latest value as c, and whatever you return becomes the new state. No stale snapshot.

Why it matters — imagine a "+3" button that stacks three updates in one click:

// BROKEN: all three read the same stale count (say 0) → 0+1 each → ends at 1, not 3
<button onClick={() => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }}>+3</button>

// CORRECT: each gets the latest value → 0→1→2→3
<button onClick={() => { setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1); }}>+3</button>

💡 Rule of thumb: if your new state depends on the old state, use the function form setCount(c => c + 1). It's always safe.


Step 6 — A small twist: can't go below 0

Let's add a rule: the counter should never show a negative number. This shows conditional logic living inside the setter.

<button onClick={() => setCount(c => (c > 0 ? c - 1 : 0))}>-</button>

What's happening: On click, the functional update runs with the latest c. The ternary c > 0 ? c - 1 : 0 says: if we're above zero, go down by one; otherwise stay at 0. So at count = 0, clicking returns 0 — React re-runs, count is still 0, screen doesn't budge. The floor is enforced by what value you put into state, not by disabling anything. (You could also disable the button — shown in the practice challenges.)

💡 Ternary condition ? a : b = a one-line if/else. Reads as "if condition then a, else b."


🔎 The full flow (recap the wiring)

Here's the finished app, click by click:

  1. Page loadsCounter() runs → useState(0) sets count = 0 → JSX shows Count: 0.
  2. Click +onClick runs setCount(c => c + 1) → setter tells React "state changed" → React re-runs Countercount is now 1re-render → screen shows Count: 1.
  3. Click −onClick runs setCount(c => (c > 0 ? c - 1 : 0)) → new value computed from latest c → setter → re-run → re-render → number goes down (never below 0).
  4. Click ResetonClick runs setCount(0) → setter → re-run → re-render → shows Count: 0.

Every single button follows the same four beats: event → setState → React re-runs the component → UI updates. You never wrote a single line that touches the number on screen directly — you only ever changed state.


✅ What you just learned

  • useState — how to create a state value and its setter: const [count, setCount] = useState(0).
  • Event handlersonClick={() => ...} attaches a function that runs on click (pass a function, not a call).
  • The setter drives re-render — calling setCount(...) is what tells React to re-run the component and update the UI.
  • You change STATE, not the DOM — the button changes state; state changing is what redraws the screen.
  • Functional updatessetCount(c => c + 1) uses the latest value and is the safe choice when new state depends on old state; setCount(count + 1) can use a stale snapshot.
  • Conditional logic in a setter — using a ternary to keep state within rules (never below 0).
  • The universal loopevent → setState → re-run → re-render. Every interactive React app is built on this.
import { useState } from 'react';

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

  return (
    <div>
      <h2>Counter</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => (c > 0 ? c - 1 : 0))}>-</button>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

export default Counter;

Reactinterview questions & answers

10 sample questions below — 222+ in the full bank inside.

How does the && operator work in JavaScript to enable conditional rendering?

&& evaluates the left side first; if it's falsy, it short-circuits and returns that falsy value without evaluating the right side. If it's truthy, it evaluates and returns the right side. In JSX, {cond && <Comp/>} renders <Comp/> only when cond is truthy, otherwise React skips rendering the falsy value.

In simple terms: It's like a security checkpoint — if the first check fails, you never even reach the second gate, and whatever failed the first check is what gets reported. Example: {isAdmin && <AdminPanel/>} — if isAdmin is false, JavaScript never even looks at <AdminPanel/>, it just returns false and React shows nothing.

When should you use the ternary operator vs the && operator for conditional rendering?

Use the ternary (cond ? A : B) when you need to render one of two things (an either/or). Use && (cond && A) when you either show something or show nothing — there's no 'else' branch.

In simple terms: Ternary is like choosing between two doors — you always walk through one of them. && is like a single door with a lock — if the condition is true, the door opens and you see the room; if false, you see nothing at all. Example: {loading ? <Spinner/> : <Content/>} (two outcomes) vs {hasNewMessages && <Badge/>} (show or nothing).

How do you do conditional rendering in React?

You render different UI based on a condition using a ternary (cond ? A : B), the && operator (cond && A), or an early return. React renders whatever the expression evaluates to.

In simple terms: It's like an if-check for what shows on screen. If the user is logged in, show "Logout"; otherwise show "Login". You write cond ? A : B or cond && A right inside JSX, and React shows whatever comes out. Example: {isLoggedIn ? <Logout/> : <Login/>}.

What happens if a component returns null?

React renders nothing for that component — no DOM node is created at all, but the component still exists in the React tree and can still re-render later if props/state change.

In simple terms: It's like a chef who gets an order but decides not to plate anything for it this round — the kitchen (React) still knows the order exists, it just serves an empty plate to the table right now. Example: function Banner({show}) { if (!show) return null; return <div>Sale!</div>; } — when show is false, nothing appears on screen.

What does 'one-way data flow' mean in React, in terms of props?

Data flows down: parent components pass data to children via props, and children can never directly change a parent's data. If a child needs to affect the parent's state, the parent passes down a callback function as a prop, and the child calls that function — the update still happens 'up' through the parent, not by the child reaching backward.

In simple terms: Think of it like a river — water (data) only flows downstream (parent to child), never upstream. If the child needs to send a message back up, it doesn't dam the river; it uses a phone (a callback prop) the parent gave it. Example: <Child onSave={handleSave} /> — Child calls props.onSave(data) to notify the parent, but never reaches into the parent's state directly.

What is the difference between a component and props?

A component is a reusable block of UI (like a Lego block). Props are the inputs you pass into that block to customise it; they flow one-way from parent to child and are read-only inside the child.

In simple terms: Think of a component like a rubber stamp — carve it once, stamp it many times. Props are the ink colour or date you set each time you stamp, so the same stamp gives slightly different output. Example: <Button label="Save" /> and <Button label="Delete" /> are the same Button with different props.

What is the children prop and when do you use it?

children is a special prop that holds whatever is nested between a component's opening and closing tags. It's used to build wrapper/layout components (like Card, Modal, Layout) that don't know in advance what content they'll display — they just render {props.children} wherever the content should appear.

In simple terms: Think of a picture frame (the wrapper component) — it doesn't care what photo you put inside it, it just displays whatever you slot in. Example: <Card><h2>Title</h2><p>Body</p></Card> — inside Card, props.children is that <h2> and <p>, and Card just renders <div className="card">{props.children}</div>.

Are props mutable inside a component? What happens if you try to change a prop directly?

No, props are read-only inside the receiving component. React expects components to be 'pure' with respect to props — a component should never modify its own props. If you need different data, ask the parent to pass new props (usually by lifting state up and re-rendering).

In simple terms: Props are like a rented car — you can drive it and use it, but you don't get to repaint it or swap the engine; if you want a different car, you go back to the rental company (the parent). Example: function Greeting(props) { props.name = 'Hacked'; ... } is an anti-pattern — mutating props.name directly doesn't reflect in the parent and breaks React's data flow assumptions.

How do you give a prop a default value in a modern function component?

Use a default parameter value in the function signature when destructuring props, e.g. function Button({ size = 'md', label }) { ... }. The old class-based defaultProps static property still works but default parameters are the current, preferred approach for function components.

In simple terms: It's like a food order form that says 'no size selected? we'll give you medium by default.' Example: function Avatar({ size = 40 }) { return <img width={size} /> } — calling <Avatar /> with no size prop uses 40, but <Avatar size={80} /> overrides it with 80.

How does optional chaining (?.) help with conditional rendering of data that might not exist yet?

Optional chaining lets you safely read nested properties that might be null/undefined without crashing — it returns undefined instead of throwing, which you can then combine with && or a fallback (?? / ternary) to conditionally render.

In simple terms: It's like knocking on a door and politely checking if anyone's home before walking in, instead of assuming the room exists and barging in (which crashes if it doesn't). Example: {user?.address?.city && <p>{user.address.city}</p>} won't crash even if user or address is still null while data is loading.

212+ more React questions inside

Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.

Unlock all questions — free

Ready to practise React?

Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.