Ace Your Interviews 🎯
Browse our collection of interview questions across various technologies.
What is React and what problem does it solve?
React is a JavaScript library for building user interfaces, created by Meta. It solves two problems:
efficiently updating the DOM when data changes — React's Virtual DOM and diffing algorithm apply minimal DOM mutations, avoiding expensive full-page repaints, and
code organization — React's component model lets you break UIs into reusable, self-contained pieces that combine data (props) and behavior (state).
What is JSX?
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like code in JavaScript. It's not HTML — it compiles to React.createElement() calls. JSX allows embedding JavaScript expressions with curly braces {}. React components return JSX. JSX elements must have one root element (or use a Fragment <>), all tags must be closed (including self-closing like <img />), and class becomes className because class is a reserved JavaScript keyword.
What is the difference between props and state?
Props are read-only data passed from a parent component to a child — the child cannot modify its own props. State is mutable data managed within a component that, when changed, causes the component to re-render. Props flow down (parent to child). State changes happen within a component via useState setters. Data that multiple components need can be 'lifted up' to their shared ancestor and passed down as props.
What is the Virtual DOM?
The Virtual DOM is React's lightweight JavaScript representation of the actual browser DOM — a tree of plain JavaScript objects. When state changes, React creates a new Virtual DOM tree, compares it to the previous one (diffing), and computes the minimum set of DOM operations needed to update the real DOM. This avoids expensive direct DOM manipulation and batches updates for performance.
What are the rules of hooks?
Two rules:
Only call hooks at the top level — never inside conditions, loops, or nested functions. React tracks hooks by call order — conditions would change the order between renders and cause bugs.
Only call hooks from React function components or custom hooks — never from regular JavaScript functions. The eslint-plugin-react-hooks enforces these rules automatically.
What is the difference between useEffect with no dependency array, empty array [], and [dep]?
No dependency array: effect runs after every render — almost always unintentional. Empty array []: effect runs once after the first render (equivalent to componentDidMount) — use for initialization, one-time subscriptions. [dep]: effect runs after first render and after any render where dep changed — use for effects that depend on a specific value (like re-fetching when a userId changes).
What is the key prop and why is it important?
The key prop is a unique identifier that React uses during list rendering to track which items have changed, been added, or been removed between renders. Without keys (or with index keys in dynamic lists), React may incorrectly reuse DOM nodes for different list items, causing UI bugs like incorrect component state or animation glitches. Keys should be stable, unique IDs — never use array indices for dynamic lists.
What is the difference between controlled and uncontrolled components?
Controlled components: React state is the single source of truth for the input value. value prop + onChange handler — React controls what the input displays. Required for real-time validation, conditional forms, and derived state. Uncontrolled components: the DOM manages the input value; React reads it via ref when needed (e.g., on submit). Simpler for basic forms but can't do real-time validation.
What does React.StrictMode do?
StrictMode is a development tool that detects potential problems. In development only (not production), it renders components twice to detect side effects that aren't safe to interrupt, warns about deprecated lifecycle methods and legacy string refs, warns about unexpected side effects in render, and logs warnings about missing useEffect cleanup. It has no visual output and adds zero overhead in production.
How do you conditionally render a component in React?
Three patterns:
Ternary operator: {condition ? <ComponentA /> : <ComponentB />} — use when you need to render one of two components.
Short-circuit &&: {condition && <Component />} — renders Component only when condition is truthy. Caution: {0 && <Comp />} renders '0', not nothing — use {!!count && <Comp />}.
Early return in the component function — return null to render nothing.
Explain the useCallback and useMemo hooks and when you'd use them.
useMemo memoizes the result of a computation — recalculates only when dependencies change. Use for expensive computations (filtering/sorting >1000 items) to avoid recalculation on every render. useCallback memoizes a function reference — returns the same function object across renders (when deps don't change). Use when passing a callback to a React.memo child component — without useCallback, a new function reference on each parent render defeats React.memo's optimization. Both are performance tools — only add them when you've measured a performance issue, not preemptively.
What is the difference between useEffect and useLayoutEffect?
Both run after render, but at different phases of the browser lifecycle. useEffect runs asynchronously after the browser has painted — non-blocking, the user sees the updated DOM before the effect runs. useLayoutEffect runs synchronously after DOM mutations but before the browser paints — blocking, runs before the user sees the update. Use useLayoutEffect only when you need to read DOM layout (getBoundingClientRect) and make synchronous DOM updates before the user sees the initial paint — for animations and tooltip positioning. useEffect for everything else.
How does Context API work and what are its performance implications?
Context provides a way to pass data through the component tree without prop drilling. createContext() creates a context. Provider wraps components that need access. useContext() reads the value in any descendant. Performance implication: when a Context value changes, every component that calls useContext() for that context re-renders — even if the specific part of the value it uses didn't change. Mitigation: split contexts by update frequency (AuthContext separate from AuthActionsContext), memoize the provider value with useMemo, or use Zustand which has built-in selector-based subscriptions.
What is TanStack Query (React Query) and why is it better than useEffect + fetch?
TanStack Query is a server state management library that handles caching, background refetching, loading states, error states, request deduplication, and mutations with cache invalidation. Compared to manual useEffect + fetch: it caches responses (no redundant API calls for the same data), background refreshes stale data, automatically retries failed requests, shares data between components without prop drilling, and handles optimistic updates. Code that takes 50 lines manually (loading state, error state, cleanup, caching) takes 5 lines with TanStack Query.
What is the difference between useState and useReducer?
useState is for simple, independent state values. useReducer is for complex state with multiple related sub-values, state transitions with names (dispatch({ type: 'ADD_ITEM' })), or when the next state depends on the previous in non-trivial ways. The reducer pattern makes state transitions explicit and testable (pure functions), matches Redux patterns for teams familiar with it, and scales better for state machines (loading/success/error). Rule of thumb: start with useState, switch to useReducer when you find yourself writing if/else or switch logic inside event handlers to manage multiple related state fields.
How do you prevent re-renders in React?
Four techniques:
React.memo — wraps a component; skips re-render if props haven't changed (shallow comparison). Effective only when parent re-renders with the same props.
useMemo — memoizes objects/arrays passed as props so their reference is stable.
useCallback — memoizes functions passed as props to React.memo children.
Context splitting — split a large context into multiple smaller ones so components only subscribe to what they need. Measure before applying — premature optimization adds complexity without guaranteed benefit.
What is the Component Error Boundary in React?
Error boundaries are React components that catch JavaScript errors in their child component tree and display a fallback UI instead of crashing the entire app. They must be class components (with componentDidCatch and getDerivedStateFromError static methods) or use the react-error-boundary library for a function component wrapper. Error boundaries catch errors during rendering, in lifecycle methods, and in constructor — but not in event handlers (use try/catch there) or async code.
How does React's reconciliation algorithm work?
Reconciliation is React's algorithm for determining what changed between renders. Two heuristics:
Element type — if the type changes (div to span, ComponentA to ComponentB), React tears down the old tree and builds a new one from scratch.
Keys — in lists, keys identify which item is which across renders, enabling reuse of DOM nodes for unchanged items. Without keys, React assumes items at the same position are the same item — correct for static lists, wrong for dynamic ones.
Explain the difference between class components and function components with hooks.
Class components use ES6 classes with lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) and this.state. Function components with hooks (since React 16.8) achieve the same with simpler syntax: useState replaces this.state, useEffect replaces lifecycle methods, useRef replaces instance variables. The React team recommends hooks for new code — they're easier to test (pure functions), enable better logic sharing (custom hooks), avoid the this binding confusion, and produce smaller bundles. Class components still work but receive no new features.
What is prop drilling and how do you solve it?
Prop drilling is passing a prop through intermediate components that don't use it themselves — only the leaf component needs it. Solutions in order of complexity:
Component composition — restructure so the leaf component is rendered by the component that owns the data, passing it as children or a render prop.
Context API — for genuinely global state (auth user, theme) that many components at different levels need.
Zustand or other global stores — for frequently-updated shared state where Context would cause performance issues.
How do you optimize a React application's performance?
In order of impact:
Identify actual bottlenecks with React DevTools Profiler — don't optimize blindly.
Code split with React.lazy + Suspense — reduce initial bundle size.
React.memo on expensive list item components with stable props.
useMemo for expensive computations.
Virtual scrolling for lists >100 items with @tanstack/react-virtual.
useTransition for non-urgent state updates.
Image optimization — lazy loading, correct sizes.
Most apps need items 1 and 2 only.
Explain React 18's Concurrent Features — what problems do they solve?
React 18 introduced concurrent rendering — the ability to prepare multiple versions of the UI simultaneously and interrupt, pause, and resume renders based on priority. useTransition marks a state update as non-urgent — React can interrupt it to handle higher-priority updates (user typing, clicks). useDeferredValue defers the re-rendering of a slow component while keeping the input it shows from current. Suspense for data fetching (with TanStack Query or Relay) enables 'render-as-you-fetch' — start fetching before the component renders. Together: UIs that never block on non-urgent work, enabling fast input + progressive rendering of slow content.
How would you architect a React application for a 20-person engineering team?
Feature-based monorepo structure: src/features/ for domain-specific code, src/components/ui/ for design system primitives. TanStack Query for all server state (centralized query key factory per feature). Zustand for global client state. React Hook Form + Zod for all forms. Path aliases (@/) for clean imports. ESLint + Prettier + TypeScript strict mode enforced in CI. MSW for test mocking. Storybook for component documentation. Each team owns a feature folder — no feature imports from another feature's internals. RTL + Vitest for testing. GitHub Actions for lint, test, build on every PR. Feature flags for safe incremental rollouts.
How do you implement code splitting in React and what are its trade-offs?
React.lazy(() => import('./Component')) splits that component into a separate JavaScript chunk downloaded on first use. Suspense provides the loading fallback while the chunk downloads. Route-based splitting (lazy loading each page/route) is the highest-impact use — reduces initial bundle by putting page-specific code in separate chunks. Component-level splitting for heavy components (Recharts, TipTap, Monaco Editor) used conditionally. Trade-off: the first load of a lazy component shows the fallback while downloading. Mitigate with preloading: on hover of a navigation link, trigger the dynamic import early — by the time they click, it's ready.
How do you handle complex forms with dynamic validation in React?
React Hook Form with zodResolver for schema-based validation. useFieldArray for dynamic field lists (multiple addresses, phone numbers). watch() for cross-field dependencies (show/hide fields based on another field's value). setError() for server-side validation errors on specific fields. useFormContext for deeply nested form components that need access to the form state without prop drilling. For multi-step forms: maintain a steps array, track currentStep in useState, validate only the current step's fields on next-step click (trigger(['field1', 'field2']) validates specific fields without submitting the form).
What are React Server Components and how do they differ from client-side React?
React Server Components (RSC) run exclusively on the server — they have access to databases, file systems, and environment variables but can't use state, effects, or event handlers. They ship zero JavaScript to the browser. Standard React components (now called Client Components, marked 'use client' in Next.js) run in the browser. RSC enables: fetching data directly in the component tree without API endpoints, keeping secrets server-side, and reducing bundle size by keeping data-fetching logic off the client. The App Router in Next.js 13+ is built on RSC — components are server-by-default, opt into client with 'use client'.
How do you implement a real-time feature (live notifications, collaborative editing) in React?
WebSocket/Socket.io: create a connection in a top-level component or Zustand store, expose a subscription method, components subscribe to relevant events. TanStack Query integration: on socket event, call queryClient.invalidateQueries() or queryClient.setQueryData() to update cached data — the UI reacts automatically. For collaborative editing: operational transforms or CRDT (Conflict-free Replicated Data Type) via Yjs + a WebSocket provider, React hook to get the shared doc. Real-time notifications: socket event updates a Zustand notifications store, NotificationBell component subscribes and shows count.
Explain how you'd write a large-scale test suite for a React application.
Three-layer testing pyramid: Unit tests (Vitest + RTL) for individual component behavior — test interactions, rendering, conditional logic. Integration tests (RTL + MSW) for feature flows — test realistic user journeys through multiple components with mocked API calls (login flow, add to cart + checkout). E2E tests (Playwright) for critical business paths — run against real browser + real backend in CI. Custom renderWithProviders wraps all tests with React Query, Router, and store providers. queryByRole-first query strategy ensures tests are accessibility-aware. Factory functions for test data generation. Target: unit + integration for all features, E2E for checkout, auth, and payment flows only.
How do you build and maintain a component design system in React?
Build on Radix UI primitives for accessible, unstyled components. Class Variance Authority (CVA) for variant-based styling (size, variant, intent). TypeScript generics for flexible props. Storybook for documentation — stories cover all variants and states, interaction tests verify behavior, accessibility addon runs axe-core. Chromatic for visual regression — screenshots every story, alerts on visual changes. Publish as a monorepo package (packages/ui). Semantic versioning with changesets. Components expose both controlled and uncontrolled APIs via forwardRef and defaultValue patterns.
What is the React Compiler (React Forget) and how does it change how you write React?
The React Compiler (stable in React 19) automatically inserts useMemo and useCallback at the bytecode level — it analyzes component code and memoizes values and functions that it determines won't change between renders. With the compiler enabled, you no longer need to manually add useMemo/useCallback for performance — write natural React code and the compiler handles memoization. The compiler also enforces the rules of React (no mutation of props, stable hook calls) and produces compile-time errors for violations. Impact: simpler code, fewer performance footguns, but you still need to understand memoization to debug compiler output and for cases the compiler can't handle.
How do you implement accessibility (a11y) in React applications?
Semantic HTML first — use button for clickable actions (not div onClick), nav for navigation, main for main content, role=alert for dynamic announcements. ARIA attributes when semantics aren't sufficient: aria-label for icon-only buttons, aria-expanded for toggles, aria-live for live regions, aria-describedby to associate error messages with inputs (the id approach: <input aria-describedby={errorId}> + <p id={errorId}>Error</p>). Focus management: when modals open, focus the first interactive element (useRef + focus()). Keyboard navigation: all interactive elements reachable by Tab, all actions triggerable by Enter/Space. Testing: axe-core (jest-axe) for automated checks, manual keyboard navigation testing.
How do you optimize the Largest Contentful Paint (LCP) in a React SPA?
The fundamental LCP problem with SPAs: the browser receives an empty HTML shell, downloads React + JS bundle, executes JS, renders components, then fetches data — LCP element appears only after all these steps. Solutions:
Use Next.js/Remix for SSR — HTML includes content, LCP is immediate.
For a pure SPA: critical above-fold images should have fetchPriority='high' and explicit width/height. Use font-display: swap. Inline critical CSS.
Preload data: include a JSON blob in the HTML for above-fold data instead of waiting for a fetch.
Code split and preload route chunks.
Use a CDN — TTFB matters for LCP.