Startup to FAANG · India 2025

React
Zero to Interview
Ready

Every topic asked in Indian interviews — from 1-year to 10-year experience. Fundamentals, hooks, performance, code splitting, polyfills, Web Vitals, SSR, Concurrent Mode, and every pattern that gets asked in Flipkart, Swiggy, Razorpay, Google, Amazon India rounds.

⚛ React 18
20 Modules
Live Code Examples
Interview Questions
Startup → FAANG
Performance Deep Dive
Module 01 · Fundamentals
● Beginner

What is React? + JSX

React is a JavaScript library (not a framework) by Meta for building UIs from composable components. It maintains a Virtual DOM, diffs it against the real DOM, and surgically updates only what changed. This is the answer to "Why React?" in every interview.

React vs Plain JS — What React Solves Concept
Library, Not Framework
React handles only the View layer. You bring your own routing (React Router), state management (Redux/Zustand), and data fetching (React Query). Angular is a full framework; React is intentionally minimal.
View only
Declarative vs Imperative
Imperative (jQuery): "Find element, change its text, add class." Declarative (React): "Given this state, the UI should look like this." React figures out HOW to update the DOM. You just describe WHAT it should look like.
Key concept
Component-Based
UI is broken into reusable, self-contained components. Each manages its own state and renders its own HTML. Components compose like Lego bricks. Large apps = many small, testable components.
Core idea
Unidirectional Data Flow
Data flows DOWN from parent to child via props. Events flow UP via callback functions. This makes data flow predictable and bugs easy to trace. Unlike Angular's two-way binding.
Props down, events up
01_jsx_basics.jsx
jsx
// JSX = JavaScript + XML syntax. Babel transforms it to React.createElement()
// These two are IDENTICAL:

// JSX way (what you write):
const el = <h1 className="title">Hello, {name}!</h1>;

// What Babel compiles it to:
const el = React.createElement('h1', { className: 'title' }, `Hello, ${name}!`);

// ─── JSX Rules ─────────────────────────────────────────────
// 1. class → className (class is reserved in JS)
// 2. for → htmlFor
// 3. Self-close all empty tags: <img /> <input /> <br />
// 4. One root element (or use Fragment <></>)
// 5. Expressions in {}, NOT statements

function UserCard({ name, age, isOnline }) {
  return (
    <>                                         {/* Fragment — no extra div */}
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <span className={isOnline ? 'online' : 'offline'}>
        {isOnline ? '● Online' : '○ Offline'}
      </span>
      {age >= 18 && <button>Adult Features</button>}  {/* conditional render */}
    </>
  );
}
🎯 Asked in: Every Company
What is the difference between React and Angular/Vue?
React is a library — only handles the View layer. Angular is a complete framework with its own router, HTTP client, forms, DI system. Vue is a progressive framework between the two. React has the largest ecosystem and is the most commonly asked in Indian product companies (Flipkart, Swiggy, Meesho, Razorpay all use React).
Module 02 · Fundamentals
● Beginner

Components & Props

Components are the building blocks of React. Props are the inputs — read-only data passed from parent to child. Understanding prop types, default props, children, and prop drilling lays the foundation for everything that follows.

02_components_props.jsx
jsx
// ─── Functional Component (modern, preferred) ──────────────
function Button({ label, onClick, variant = 'primary', disabled = false }) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  );
}

// ─── Children prop ─────────────────────────────────────────
function Card({ title, children }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-body">{children}</div>  {/* slot pattern */}
    </div>
  );
}
// Usage: <Card title="Profile"><UserCard /></Card>

// ─── Props spreading (careful — don't overuse) ─────────────
function Input({ label, ...rest }) {     // rest = remaining props
  return (
    <label>
      {label}
      <input {...rest} />              {/* spread all HTML attrs */}
    </label>
  );
}

// ─── Prop types of data you can pass ──────────────────────
<MyComponent
  str="hello"           // string
  num={42}             // number (curly braces!)
  bool={true}          // boolean (or just write: bool)
  arr={[1, 2, 3]}      // array
  obj={{ key: 'val' }} // object
  fn={() => {}}         // function
  jsx={<Icon />}       // JSX element
/>
🎯 Startup round
What is prop drilling and how do you avoid it?
Prop drilling is passing props through many intermediate components that don't need them, just to reach a deeply nested child. Solutions: (1) Context API for global/shared state. (2) Component composition — pass the child component directly instead of its data. (3) State management libraries (Redux, Zustand). Prefer composition first, then Context, then Redux.
Module 03 · Fundamentals
● Beginner

State & Event Handling

State is private, mutable data owned by a component. When state changes, React re-renders the component. Understanding when to use state vs props, and how events work, is fundamental for every interview.

03_state_events.jsx
jsx
import { useState } from 'react';

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

  // ─── NEVER mutate state directly ──────────────────────
  // ❌ count++            (mutation — won't trigger re-render)
  // ✅ setCount(count + 1) (creates new value, triggers re-render)

  // ─── Functional update (when new state depends on old) ─
  const increment = () => setCount(prev => prev + 1);  // safe

  // ─── State batching (React 18 batches everything) ──────
  const handleClick = () => {
    setCount(c => c + 1);  // batched — only ONE re-render total
    setCount(c => c + 1);  // functional updates still stack correctly
  };                         // count increases by 2, but 1 re-render

  // ─── Object state — spread to preserve other keys ─────
  const [form, setForm] = useState({ name: '', email: '' });
  const handleChange = (e) => setForm(prev => ({
    ...prev,                    // keep other fields
    [e.target.name]: e.target.value  // update only this field
  }));

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      {/* Synthetic events — React wraps native events */}
      <input onChange={handleChange} name="name" />
      <form onSubmit={(e) => { e.preventDefault(); }}> {/* prevent default! */}
    </div>
  );
}
🎯 Very common — Flipkart, Meesho
Why doesn't React state update immediately after calling setState?
State updates in React are asynchronous and batched. When you call setState, React schedules a re-render — it doesn't happen synchronously. After the function finishes, React batches all state updates and does one re-render. If you need the new state value immediately, use a useEffect with the state as a dependency, or use functional updates like setState(prev => prev + 1).
Module 04 · Fundamentals
● Beginner → Mid

Virtual DOM & Reconciliation

The Virtual DOM is React's secret weapon. Understanding it — and the diffing algorithm — is asked in almost every interview in India, from startups to Google. This is where React's performance story begins.

Virtual DOM → Diff → Patch Real DOM Animated

React keeps two VDOMs: previous and next. It diffs them (O(n) with heuristics), finds the minimal set of DOM operations, then batches and applies them to the real DOM.

Real DOM update = Expensive (reflow + repaint + layout recalculation) Virtual DOM diff = Cheap (plain JS object comparison in memory) React's work = diff VDOMs → find minimum changes → apply to real DOM
Diffing Algorithm (Reconciliation)
React's O(n) heuristics: (1) If element type changes, tear down and rebuild the subtree entirely. (2) Same type = update attributes/props in place. (3) Lists use the key prop to match old and new items without re-mounting.
O(n) not O(n³)
The key Prop
Without keys, React re-renders the entire list on any change. With stable keys (IDs, not array index), React matches existing components to new ones → only mounts/unmounts what actually changed. Never use array index as key if list can reorder.
CriticalNever use index
React Fiber
React 16's rewrite of the reconciler. Breaks rendering work into small "fibers" (units of work) that can be paused, resumed, prioritized, or abandoned. Enables concurrent mode, Suspense, and time-slicing. Prioritizes user interactions over background rendering.
Concurrent Mode
🎯 Google, Amazon, Flipkart — very common
Explain Virtual DOM. Why is it faster than the real DOM?
The Virtual DOM is a lightweight JavaScript object representation of the real DOM. When state changes: (1) React creates a new VDOM tree. (2) Diffs it against the previous VDOM (reconciliation) — this is pure JS, very fast. (3) Computes minimal DOM mutations. (4) Applies them in a single batch to the real DOM. The real DOM is expensive because every change can trigger reflow (layout recalculation) and repaint. Batching updates via the VDOM minimizes these expensive operations.
Module 05 · Fundamentals
● Beginner → Mid

Component Lifecycle & Hooks Overview

Class components had lifecycle methods. Hooks replaced them for functional components. You must know both — class components still exist in old codebases and lifecycle questions still appear in interviews.

Class LifecycleHook EquivalentWhen It Runs
constructor()useState / useReducerComponent created — initialize state
componentDidMount()useEffect(() => {}, [])After first render — fetch data, subscriptions
componentDidUpdate()useEffect(() => {}, [deps])After every render when deps changed
componentWillUnmount()useEffect cleanup (return fn)Before component removed — cleanup timers, listeners
shouldComponentUpdate()React.memo / useMemoDecide whether to re-render
render()return JSX from functionEvery render
componentDidCatch()No hook equivalentError boundary — still needs class component
💡 Key Interview Point

Error Boundaries cannot be implemented with hooks — they still require class components with componentDidCatch and getDerivedStateFromError. This is the one remaining reason you'd write a class component today.

Module 06 · Core Hooks
◆ Mid Level

useState & useReducer

useState is for simple state. useReducer is for complex state with multiple related updates — the same pattern as Redux but local to one component. Choosing between them correctly shows seniority.

06_state_hooks.jsx
jsx
// ─── useState — for simple, independent pieces of state ────
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);
const [items, setItems] = useState([]);

// Lazy initializer — function runs ONCE (avoids expensive recompute)
const [data, setData] = useState(() => JSON.parse(localStorage.getItem('data')));

// ─── useReducer — for complex state logic ──────────────────
const initialState = { count: 0, loading: false, error: null };

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT': return { ...state, count: state.count + 1 };
    case 'SET_LOADING': return { ...state, loading: action.payload };
    case 'SET_ERROR':  return { ...state, error: action.payload, loading: false };
    default: return state;
  }
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const fetchData = async () => {
    dispatch({ type: 'SET_LOADING', payload: true });
    try {
      const data = await api.fetch();
      dispatch({ type: 'INCREMENT' });
    } catch (e) {
      dispatch({ type: 'SET_ERROR', payload: e.message });
    }
  };
}
🎯 Swiggy, Razorpay — common
When do you use useReducer over useState?
Use useReducer when: (1) Next state depends on previous state in complex ways. (2) Multiple pieces of state update together (loading, error, data — the "fetch" pattern). (3) State transitions have named actions — makes debugging much easier. (4) You have 3+ related useState calls that always update together. Rule of thumb: if your setState logic is complex enough to have bugs, move it to a reducer.
Module 07 · Core Hooks
◆ Mid Level

useEffect — Deep Dive

useEffect is the most misunderstood hook. It's NOT a lifecycle method replacement — it's a synchronization tool. Getting the dependency array right, cleanup functions, and avoiding race conditions are key interview topics.

07_useeffect.jsx
jsx
import { useState, useEffect } from 'react';

// ─── Three forms of useEffect ─────────────────────────────
useEffect(() => {});              // runs after EVERY render (rarely want this)
useEffect(() => {}, []);          // runs ONCE after mount (componentDidMount)
useEffect(() => {}, [userId]);    // runs when userId changes

// ─── Data fetching with cleanup (race condition fix) ───────
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let cancelled = false;        // ← race condition fix

    async function fetchUser() {
      const data = await fetch(`/api/users/${userId}`).then(r => r.json());
      if (!cancelled) setUser(data);  // only set if component still mounted
    }

    fetchUser();
    return () => { cancelled = true; }; // cleanup: cancel stale request
  }, [userId]);                             // re-fetch when userId changes

  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

// ─── Event listeners with cleanup ─────────────────────────
useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize); // ← critical!
}, []);
⚠️ Most Common useEffect Mistakes

1. Missing dependency: using a variable in the effect but not listing it → stale closure bug. ESLint exhaustive-deps catches this.
2. Object/array in deps: {a:1} !== {a:1} in JS → infinite loop. Use useMemo or move object inside effect.
3. No cleanup: event listeners, timers, subscriptions leak memory → always return cleanup function.
4. Async function directly: useEffect callback can't be async — create an inner async function.

🎯 Very common — all levels
What is a stale closure in useEffect?
A stale closure occurs when an effect captures a variable from its outer scope, but that variable later changes and the effect still uses the old value. Example: useEffect(() => { setInterval(() => console.log(count), 1000) }, []) — count will always log 0 because the effect closed over the initial value. Fix: add count to deps array, or use useRef to store the latest value, or use functional state updates.
Module 08 · Core Hooks
◆ Mid Level

useRef, useContext & useMemo

08_hooks.jsx
jsx
// ─── useRef — persists value without re-render ─────────────
const inputRef = useRef(null);         // DOM reference
const timerRef = useRef(null);         // mutable value, NO re-render on change
const prevCount = useRef(count);       // track previous value

// DOM access:
<input ref={inputRef} />
const focus = () => inputRef.current.focus();  // imperatively focus input

// ─── useContext — consume context without Consumer wrapper ─
const ThemeContext = React.createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  );
}

function Child() {
  const theme = useContext(ThemeContext);  // → "dark"
  return <div className={theme}>...</div>;
}

// ─── useMemo — memoize expensive computation ───────────────
const sortedList = useMemo(() => {
  return [...items].sort((a, b) => a.price - b.price);  // expensive sort
}, [items]);                                                // only re-sort when items changes

// ─── useCallback — memoize function reference ──────────────
const handleDelete = useCallback((id) => {
  setItems(prev => prev.filter(item => item.id !== id));
}, []);  // stable reference — won't cause child re-renders

// Pass to React.memo children to prevent unnecessary re-renders
const MemoizedList = React.memo(ExpensiveList);
🎯 Mid-Senior — Swiggy, Zepto, PhonePe
Difference between useMemo and useCallback?
useMemo memoizes a computed value (returns the value). useCallback memoizes a function reference (returns the function). useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). Use useMemo to avoid expensive recalculation. Use useCallback to give stable function references to memoized child components so they don't re-render when the parent re-renders.
Module 09 · Core Hooks
◆ Mid Level

Custom Hooks

Custom hooks are functions starting with "use" that encapsulate and reuse stateful logic across components. They're the modern replacement for HOCs and render props. Building one from scratch is a common interview task.

09_custom_hooks.jsx — Common hooks you'll build in interviews
jsx
// ─── useFetch — data fetching hook ────────────────────────
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(r => r.json())
      .then(d => { if(!cancelled) { setData(d); setLoading(false); }})
      .catch(e => { if(!cancelled) setError(e); });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}
// Usage: const { data, loading } = useFetch('/api/users');

// ─── useDebounce — search input optimization ───────────────
function useDebounce(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);  // cancel on every keystroke
  }, [value, delay]);
  return debounced;
}
// API call only fires 300ms after user stops typing!

// ─── useLocalStorage ───────────────────────────────────────
function useLocalStorage(key, initialValue) {
  const [stored, setStored] = useState(() => {
    try { return JSON.parse(localStorage.getItem(key)) ?? initialValue; }
    catch { return initialValue; }
  });
  const setValue = (value) => {
    setStored(value);
    localStorage.setItem(key, JSON.stringify(value));
  };
  return [stored, setValue];
}

// ─── useOnClickOutside — dropdown/modal close ─────────────
function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = (e) => { if (!ref.current?.contains(e.target)) handler(e); };
    document.addEventListener('mousedown', listener);
    return () => document.removeEventListener('mousedown', listener);
  }, [ref, handler]);
}
Module 10 · Patterns
◆ Mid → Senior

HOC, Render Props & Compound Components

Advanced component patterns asked in senior rounds. HOCs and Render Props are older patterns — important to know conceptually. Compound components are still actively used (Radix UI, Headless UI use them).

10_patterns.jsx
jsx
// ─── HOC — wraps a component, adds behavior ────────────────
function withAuth(WrappedComponent) {
  return function AuthGuard(props) {
    const { isLoggedIn } = useAuth();
    if (!isLoggedIn) return <Navigate to="/login" />;
    return <WrappedComponent {...props} />;
  };
}
const ProtectedDashboard = withAuth(Dashboard); // HOC composed

// ─── Render Props — pass render function as prop ───────────
function DataProvider({ url, render }) {
  const { data, loading } = useFetch(url);
  return render({ data, loading });  // caller controls the UI
}
// Usage:
<DataProvider
  url="/api/users"
  render={({ data, loading }) => loading ? <Spinner/> : <UserList data={data} />}
/>

// ─── Compound Components — Tab/Accordion pattern ───────────
const TabContext = React.createContext();

function Tabs({ children }) {
  const [active, setActive] = useState(0);
  return <TabContext.Provider value={{ active, setActive }}>{children}</TabContext.Provider>;
}
function TabList({ children }) { return <div role="tablist">{children}</div>; }
function Tab({ index, children }) {
  const { active, setActive } = useContext(TabContext);
  return <button onClick={() => setActive(index)} aria-selected={active===index}>{children}</button>;
}
Tabs.List = TabList; Tabs.Tab = Tab;
// Usage: <Tabs><Tabs.List><Tabs.Tab index={0}>...</Tabs.Tab></Tabs.List></Tabs>
Module 11 · Patterns
◆ Mid → Senior

State Management

The state management landscape has evolved dramatically. Context API, Redux Toolkit, Zustand, and React Query each solve different problems. Knowing which to use when is a top senior interview question.

ToolBest ForWhen to UseDrawback
useStateLocal UI stateForm, toggle, modal open/closeCan't share across tree
Context APILow-frequency global stateTheme, auth, localeRe-renders all consumers
Redux ToolkitComplex global stateLarge app, many features sharing state, time-travel debugBoilerplate, learning curve
ZustandSimple global stateStartup/mid app, replaces Context + ReduxLess structured than Redux
React Query / TanStackServer state (API data)Always — handles cache, loading, refetch, staleOnly for server data
Jotai / RecoilAtomic stateFine-grained updates, avoids re-render problemsLess mature ecosystem
💡 The Golden Rule

Server state ≠ Client state. Use React Query / TanStack Query for any data from an API — it handles caching, background refetching, loading/error states automatically. Use Zustand or Context for pure UI state (sidebar open, selected filters). Mixing them into one Redux store is the old way.

11_zustand_example.jsx — Modern approach
jsx
// Zustand — simplest global state (no Provider needed!)
import { create } from 'zustand';

const useCartStore = create((set, get) => ({
  items: [],
  total: 0,
  addItem: (item) => set(state => ({ items: [...state.items, item] })),
  removeItem: (id) => set(state => ({ items: state.items.filter(i => i.id !== id) })),
  clearCart: () => set({ items: [], total: 0 }),
}));

// In any component — no Provider, no connect():
function Cart() {
  const { items, addItem, removeItem } = useCartStore();
  return <div>{items.length} items in cart</div>;
}
Module 12 · Patterns
◆ Mid Level

React Router

React Router v6 is the standard for client-side routing. Nested routes, protected routes, dynamic params, and lazy-loaded routes are common interview topics and real-world patterns.

12_router.jsx — React Router v6
jsx
import { BrowserRouter, Routes, Route, useParams, useNavigate,
         useSearchParams, Navigate, Outlet } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/user/:id" element={<UserPage />} />
        {/* Protected route pattern */}
        <Route element={<ProtectedLayout />}>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

// Protected Layout using Outlet
function ProtectedLayout() {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" replace />;
  return <Outlet />;  {/* renders child routes */}
}

// Hooks
const { id } = useParams();                           // /user/123 → id = "123"
const navigate = useNavigate();                       // navigate('/home')
const [params, setParams] = useSearchParams();       // ?page=2 → params.get('page')
Module 13 · Performance
◈ Senior Level

Re-renders, Memoization & Optimization

A component re-renders when: state changes, props change, parent re-renders, or context value changes. Senior interviews at Swiggy, Razorpay, and FAANG dig deep into when to prevent re-renders and how.

When React Re-renders — Decision Tree Visual
13_optimization.jsx
jsx
// ─── React.memo — skip re-render if props unchanged ────────
const ExpensiveChild = React.memo(({ items, onDelete }) => {
  return items.map(item => <Item key={item.id} {...item} onDelete={onDelete} />);
});

function Parent() {
  const [count, setCount] = useState(0);
  const [items] = useState([...]);

  // ❌ Without useCallback: new function ref on every parent render
  // → ExpensiveChild re-renders even when count changes (not items)
  // ✅ With useCallback: stable reference, ExpensiveChild skips re-render
  const handleDelete = useCallback((id) => {
    setItems(prev => prev.filter(i => i.id !== id));
  }, []);

  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <ExpensiveChild items={items} onDelete={handleDelete} />
    </>
  );
}

// ─── Virtualization — for huge lists ──────────────────────
import { FixedSizeList } from 'react-window';

const Row = ({ index, style }) => <div style={style}>Row {index}</div>;

// Only renders visible rows — 10,000 items, no lag
<FixedSizeList height={600} itemCount={10000} itemSize={35} width="100%">
  {Row}
</FixedSizeList>
React.memo
Wraps a component. Shallowly compares props before re-rendering. Skip re-render if props didn't change. Only useful for components that re-render often AND their parents re-render with same props. Don't memoize everything — it costs memory.
Shallow compare
Context Performance
When a Context value changes, ALL consumers re-render — even ones that only use part of the value. Fix: split context into smaller pieces (ThemeContext, AuthContext, SettingsContext). Or use useMemo on the context value object.
Split contextsCommon gotcha
Virtualization (react-window)
Rendering 10,000 list items at once = thousands of DOM nodes = crash. Virtualization renders only the visible rows (windowing). React Window and React Virtual are the go-to libraries. Essential for any list with 100+ items.
Windowingreact-window
Module 14 · Performance
◈ Senior Level

Code Splitting & Lazy Loading

Your entire React app ships as one JS bundle by default. Code splitting breaks it into smaller chunks loaded on demand. This is one of the highest-impact optimizations — asked in every senior interview in India.

Bundle Without vs With Code Splitting Visual
14_code_splitting.jsx
jsx
import { lazy, Suspense } from 'react';

// ─── Route-based splitting (biggest impact) ────────────────
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings  = lazy(() => import('./pages/Settings'));
const Reports   = lazy(() => import('./pages/Reports'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>  {/* shown while chunk loads */}
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings"  element={<Settings />} />
      </Routes>
    </Suspense>
  );
}
// Users only download Dashboard.chunk.js when they visit /dashboard

// ─── Component-based splitting (heavy components) ──────────
const HeavyChart = lazy(() => import('./components/HeavyChart'));
const RichEditor = lazy(() => import('./components/RichEditor'));

// ─── Preloading — start loading before user navigates ──────
const preloadDashboard = () => import('./pages/Dashboard'); // fire early
<NavLink onMouseEnter={preloadDashboard} to="/dashboard">Dashboard</NavLink>;
// By the time they click, chunk is already loaded!

// ─── Named chunks (webpack magic comment) ──────────────────
import(/* webpackChunkName: "analytics" */ './Analytics');
What to Split
Routes (always), heavy libraries (chart.js, Monaco editor, PDF viewer), conditionally rendered features (admin panel), features behind feature flags. Don't split tiny components — chunk overhead not worth it.
Routes first
Suspense Boundaries
Place Suspense closest to the lazy component, not at app root. Multiple Suspense boundaries give granular loading states. The fallback is shown while the chunk downloads. Nest them for different loading strategies per section.
Granular
Bundle Analysis
webpack-bundle-analyzer and vite-bundle-visualizer show you exactly what's in your bundle. Run before optimizing — you'll find massive libraries (moment.js, lodash) that can be swapped or tree-shaken. Always measure before and after.
Measure first
🎯 Very common — FAANG, Swiggy, Razorpay
What is code splitting and how do you implement it in React?
Code splitting breaks the JS bundle into smaller chunks loaded on demand, reducing initial bundle size. Implementation: (1) React.lazy() + Suspense for component-level splitting. (2) Dynamic import() for any module. (3) Webpack/Vite automatically creates separate chunks. The biggest win is route-based splitting — users only download code for pages they visit. For a 2MB app, this can reduce initial load to 200KB.
Module 15 · Performance
◈ Senior Level

Core Web Vitals & React Profiling

Google's Core Web Vitals directly affect SEO rankings and user experience. Understanding them and how React affects each metric is now a standard senior-level question in India.

Core Web Vitals — What They Measure Reference
MetricWhat it measuresGood thresholdReact impact
LCPLargest Contentful Paint — when main content loads< 2.5sSSR, lazy images, code splitting, preloading
INPInteraction to Next Paint — how fast UI responds to clicks< 200msAvoid long tasks, use startTransition, defer non-critical work
CLSCumulative Layout Shift — elements moving unexpectedly< 0.1Set image dimensions, avoid injecting content above fold
FCPFirst Contentful Paint — any content visible< 1.8sSSR, skeleton screens, reduce blocking JS
TTFBTime To First Byte — server response speed< 800msSSR/SSG, CDN, caching
15_profiling.jsx — React Profiler + Web Vitals
jsx
// ─── React DevTools Profiler (in code) ────────────────────
import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration) {
  console.log(`${id} (${phase}): ${actualDuration.toFixed(2)}ms`);
}
// Wrap expensive components to measure render time:
<Profiler id="ProductList" onRender={onRenderCallback}>
  <ProductList />
</Profiler>

// ─── Web Vitals measurement ────────────────────────────────
import { onLCP, onINP, onCLS, onFCP } from 'web-vitals';

onLCP(metric => sendToAnalytics(metric));
onINP(metric => sendToAnalytics(metric));
onCLS(metric => sendToAnalytics(metric));

// ─── startTransition — mark non-urgent updates ────────────
import { startTransition } from 'react';

const handleSearch = (query) => {
  setSearchInput(query);           // urgent — update input immediately
  startTransition(() => {
    setFilteredResults(filter(query));  // non-urgent — can be interrupted
  });                              // typing stays responsive!
};
Module 16 · Advanced
◈ Senior Level

SSR, SSG, CSR & Hydration

Rendering strategy is a major architect-level question. SSR, SSG, CSR — knowing trade-offs and how React handles hydration is essential for senior roles at product companies.

CSR — Client Side Rendering
Browser downloads empty HTML + JS bundle. JS executes, React renders the UI. Fast interactivity after load, but: slow initial paint (blank screen), bad SEO (crawlers see empty page). Default for Create React App. Good for: admin dashboards, authenticated apps.
Slow initialBad SEO
SSR — Server Side Rendering
Server renders React to HTML, sends full HTML to browser. User sees content immediately (good LCP). Then React "hydrates" — attaches event handlers to server-rendered HTML. Good for: e-commerce, news sites, anything needing fast first paint + SEO. Next.js does this.
Fast paintGood SEO
SSG — Static Site Generation
HTML generated at BUILD time, not request time. Served instantly from CDN. Fastest possible load time. Limitation: can't personalize per-user, must rebuild to update content. Good for: blogs, landing pages, docs. ISR (Next.js) adds incremental updates.
FastestCDN friendly
Hydration
After SSR sends HTML, React re-runs on the client — builds the Virtual DOM, matches it to server-sent HTML (reconciles), and attaches event handlers. If server and client HTML don't match → hydration mismatch error. Time between HTML visible and interactive = "hydration gap".
Critical concept
🎯 Senior — Amazon, Flipkart, Licious
What is hydration? What causes hydration errors?
Hydration is React's process of attaching event handlers to server-rendered HTML without re-rendering it. React runs on the client, builds its VDOM, and "hydrates" (adopts) the existing server HTML. Hydration errors occur when the server-rendered HTML doesn't match what React expects on the client — caused by: (1) Different data on server vs client (Date.now(), window object). (2) Browser-only APIs in render logic. (3) Conditional rendering that differs between environments. Fix: use suppressHydrationWarning for intentional mismatches, or useEffect for client-only code.
Module 17 · Advanced
◈ Senior Level

React 18 & Concurrent Mode

React 18 is the current version with major new features. Understanding Automatic Batching, startTransition, Suspense for data fetching, and React Server Components is now expected in senior Indian interviews.

Automatic Batching
React 17: batched updates only in event handlers. React 18: batches ALL updates — setTimeout, Promises, native events. Multiple setStates in an async function = ONE re-render. Massive performance improvement with zero code changes.
Free perf winReact 18+
startTransition
Mark state updates as "non-urgent." React can interrupt and abandon them to handle urgent updates (user input) first. Use for: filtering large lists, navigating, any update that doesn't need to be immediate. The typing stays smooth even if the filter is slow.
Concurrent
Suspense for Data Fetching
Suspend a component while it waits for data — show fallback. Works with React Query, Relay, Next.js. The component "throws a promise" — React catches it, shows Suspense fallback, then re-renders when data is ready. Clean loading UX without loading booleans everywhere.
React 18+
React Server Components (RSC)
Components that run only on the server — zero JS sent to browser. Access databases directly, use server-only APIs. Client Components use 'use client' directive. Requires Next.js App Router. The future of React — completely changes the mental model. RSC = no useState, no hooks.
Next.js 13+
useId Hook
Generates stable, unique IDs that match between server and client (no hydration mismatch). Use for accessibility — connecting label htmlFor to input id. Don't use for list keys.
React 18+
useDeferredValue
Like startTransition but for values, not updates. Pass a slow-to-compute value; React defers recalculating it during urgent updates. The UI stays responsive while the deferred value "catches up." Good for search suggestions, previews.
Concurrent
17_react18.jsx
jsx
import { startTransition, useDeferredValue, useTransition, useId } from 'react';

// ─── useTransition — shows pending state ──────────────────
function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (q) => {
    setQuery(q);                              // urgent: update input
    startTransition(() => {
      setResults(heavyFilter(q));           // non-urgent: filter results
    });
  };

  return (
    <>
      <input value={query} onChange={e => handleSearch(e.target.value)} />
      {isPending ? <Spinner/> : <ResultList items={results} />}
    </>
  );
}

// ─── createRoot — React 18 required ───────────────────────
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// react-dom/client (NOT react-dom) enables concurrent features
Module 18 · Advanced
◈ Senior Level

Polyfills, Browser Compatibility & Web APIs

Polyfills provide modern functionality to older browsers. Understanding what needs polyfilling, how Babel targets work, and which browser APIs need fallbacks is a senior-level concept asked in companies building for broad user bases (like Jio, BSNL network users with older phones).

What is a Polyfill?
A polyfill is code that implements a modern Web API in browsers that don't support it natively. Example: Promise.allSettled doesn't exist in older browsers — a polyfill adds it. The term comes from "Polyfilla" (wall filler) — fills in the gaps in browser support.
Browser compat
Babel + @babel/preset-env
Transpiles modern JS syntax (arrow functions, classes, optional chaining) to ES5 for old browsers. With browserslist config ("> 0.5%, last 2 versions, IE 11"), Babel knows WHAT to transpile. Syntax transpilation ≠ polyfilling — you need both separately.
Transpilation
core-js Polyfills
Add useBuiltIns: "usage" in babel.config to auto-import polyfills for only the browser APIs you actually use. core-js/stable polyfills Promise, Array.from, Object.assign, fetch, etc. Don't include all of core-js — it's huge. Targeted polyfilling only.
core-js
IntersectionObserver Polyfill
Used for lazy loading images and infinite scroll. Not available in IE11. Polyfill: intersection-observer package. Alternatively, use event listeners on scroll — less efficient but works everywhere. Most modern apps target Chrome 60+ and skip IE entirely now.
Lazy loading
18_polyfills_compat.js
js
// ─── browserslist (.browserslistrc) ───────────────────────
// > 0.5%           - browsers with >0.5% market share
// last 2 versions  - last 2 major versions
// not dead         - not discontinued
// not IE 11        - exclude IE11 (most apps now)

// ─── babel.config.js with automatic polyfilling ───────────
module.exports = {
  presets: [['@babel/preset-env', {
    useBuiltIns: 'usage',   // auto-import only used polyfills
    corejs: 3,              // core-js version
    targets: "> 0.5%, last 2 versions, not dead"
  }]]
};

// ─── Manual polyfill pattern ──────────────────────────────
if (!window.IntersectionObserver) {
  require('intersection-observer');  // load polyfill if needed
}

// ─── fetch polyfill for older browsers ───────────────────
import 'whatwg-fetch';  // polyfills window.fetch

// ─── Feature detection (better than browser detection) ────
if ('IntersectionObserver' in window) {
  // use modern API
} else {
  // fallback
}

// ─── Common APIs needing polyfills ────────────────────────
// Promise.allSettled  → core-js
// Array.flat/flatMap  → core-js
// Object.fromEntries  → core-js
// ResizeObserver      → resize-observer-polyfill
// URL API             → core-js
// AbortController     → abortcontroller-polyfill
Module 19 · Advanced
◈ Senior Level

Testing in React

Testing knowledge is expected at senior levels. React Testing Library (RTL) is the industry standard — it tests behavior, not implementation. Understanding unit tests, integration tests, and mocking is commonly asked.

19_testing.jsx
jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';

// ─── Basic component test ─────────────────────────────────
test('renders user name', () => {
  render(<UserCard name="Rahul" age={25} />);
  expect(screen.getByText('Rahul')).toBeInTheDocument();
  expect(screen.getByText('Age: 25')).toBeInTheDocument();
});

// ─── User interaction test ────────────────────────────────
test('counter increments on click', async () => {
  const user = userEvent.setup();
  render(<Counter />);

  expect(screen.getByText('Count: 0')).toBeInTheDocument();
  await user.click(screen.getByRole('button', { name: /increment/i }));
  expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

// ─── Async test with API mock ─────────────────────────────
vi.mock('../api', () => ({
  fetchUser: vi.fn().mockResolvedValue({ name: 'Priya', email: 'p@co.in' })
}));

test('displays fetched user', async () => {
  render(<UserProfile userId="1" />);
  expect(screen.getByText(/loading/i)).toBeInTheDocument();
  await waitFor(() => expect(screen.getByText('Priya')).toBeInTheDocument());
});

// ─── Query priority (RTL guideline) ──────────────────────
// 1. getByRole       - accessibility-based (best)
// 2. getByLabelText  - form labels
// 3. getByPlaceholder
// 4. getByText       - visible text
// 5. getByTestId     - last resort (not user-visible)
Module 20 · Interview Mastersheet

Complete Interview Question Bank

Every question that gets asked in Indian tech interviews — from 1-year experience startups to 5+ year FAANG rounds. Grouped by difficulty and company level.

Startup / 1-2 years experience
What is React? How is it different from a framework?
Library, not framework. Only handles View layer. No built-in router, HTTP, DI. You compose your own stack.
Easy
What is the Virtual DOM?
JS object tree, diffed against previous version, minimal real DOM updates applied. 10x faster than naive DOM manipulation.
Easy
What are hooks? Why were they introduced?
Functions to use state + side effects in function components. Replaced class components. Better code reuse, simpler testing.
Easy
useState vs useReducer?
useState for simple independent state. useReducer when state transitions are complex, multiple sub-values update together.
Easy-Mid
What is prop drilling?
Passing props through intermediate components that don't use them. Fix: Context API, Zustand, or component composition.
Easy
Controlled vs Uncontrolled components?
Controlled: React state drives input value (onChange + value). Uncontrolled: DOM drives itself, read via ref. Controlled is preferred.
Easy
Mid Level / 2-4 years — Swiggy, Razorpay, Meesho
How does useEffect cleanup work?
Return a function from useEffect — runs before next effect execution or component unmount. Use to cancel fetch, clear timers, remove listeners.
Mid
What causes unnecessary re-renders?
New object/array reference in props, inline functions, parent re-render. Fix: useMemo, useCallback, React.memo, state colocation.
Mid
What is React.memo? When NOT to use it?
Skips re-render if props shallowly equal. Don't use: when props change often, when component is cheap, when it makes code complex.
Mid
Explain the key prop and why index is bad
Keys help reconciler match old/new list items. Array index as key → wrong components reused on insert/delete/reorder → UI bugs.
Mid
Context API vs Redux vs Zustand?
Context: low-frequency global state (theme, auth). Redux: large apps needing predictable state + devtools. Zustand: simple global state without boilerplate.
Mid
How to implement debounce in React?
Custom useDebounce hook with useEffect + setTimeout. Clear timer in cleanup. Call API only 300ms after user stops typing.
Mid — coding round
Senior / 4+ years — FAANG, Flipkart, PhonePe, Google
Explain React Fiber and Concurrent Mode
Fiber: incremental rendering — work split into units, can pause/resume/prioritize. Concurrent: multiple UI versions simultaneously, interrupts low-priority renders for high-priority (user input).
Senior
Code splitting strategy in large apps?
Route-based (always first), then component-based for heavy UI, then library-based (chart.js, monaco). Preload on hover. Analyze bundle first with bundle-analyzer.
Senior
SSR vs SSG vs CSR — when to use each?
CSR: auth-only apps, admin. SSR: e-commerce (SEO + fresh data). SSG: blogs, docs (static + fast). ISR: SSG with periodic revalidation. Hybrid: different strategies per page.
Senior
How do you improve LCP in a React app?
SSR/SSG, preload critical assets, lazy load below-fold content, optimize images (next/image), reduce JS blocking, code split, use CDN for static assets.
Senior
What are React Server Components?
Components rendered on server only — zero client JS. Access DB directly. No useState, no useEffect. Client Components ('use client') still hydrate normally. RSC + Client Components coexist.
Senior
Design a scalable React app architecture
Feature-based folder structure. Colocation. Compound components for UI library. React Query for server state. Zustand for UI state. Lazy load routes. Error boundaries per section. Monorepo (Turborepo) for large teams.
System design
🎯 React Interview Readiness Checklist

You're ready when you can answer all of these without hesitation:

  • Explain Virtual DOM, reconciliation, and the key prop from scratch
  • Write a custom hook (useFetch, useDebounce, useLocalStorage) in a live coding round
  • Explain exactly when a component re-renders and how to prevent unnecessary ones
  • Describe code splitting strategy for a large e-commerce app
  • Explain SSR hydration — what it is, why it matters, what causes errors
  • Compare Context API, Redux Toolkit, and Zustand — when to use each
  • Explain what startTransition does and when you'd use it
  • Describe Core Web Vitals and how React choices affect each metric
  • Build a protected route with React Router v6
  • Explain React Server Components and how they differ from Client Components
  • Describe what polyfills are and how Babel + browserslist automate them