Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
React Interview Questions (68+ Deep Dive Q&A)
1. Core Architecture (Fiber)
1. What is React Fiber? (Visualized)
1. What is React Fiber? (Visualized)
child- first childsibling- next siblingreturn- parent
type, stateNode (DOM node or class instance), pendingProps, memoizedState (hooks linked list), effectTag (what changed), lanes (priority bits).Phases:- Render Phase (Async, Interruptible): Walks the Fiber tree, diffs current vs
workInProgress, calculates changes. This phase is pure — no side effects, no DOM mutations. Can be paused viashouldYield()(checks if the browser needs the main thread back, typically every ~5ms usingrequestIdleCallbackorMessageChannel). - Commit Phase (Sync, Uninterruptible): Applies all collected effects to the real DOM in one batch. Runs lifecycle methods (
componentDidMount,useLayoutEffect). Cannot be interrupted because partial DOM updates would leave the UI in a broken state.
current (what is on screen) and workInProgress (being built). On commit, React swaps pointers: workInProgress becomes current. This is analogous to double buffering in graphics rendering.What interviewers are really testing: Whether you understand why React needed a rewrite (not just what Fiber is), and whether you grasp the render/commit phase split and its implications for side effects.Red flag answer: “Fiber is React’s virtual DOM” or “It makes React faster.” Fiber is specifically about the reconciler architecture, not the virtual DOM concept itself. And it does not make React faster per se — it makes React more responsive by breaking work into chunks.Follow-up:- Why can’t the commit phase be interruptible? What would go wrong if React paused mid-DOM-update?
- How does Fiber’s priority system (
lanes) decide which updates to process first? Give an example of a high-priority vs low-priority update. - In React 16-17,
componentWillMount/componentWillUpdatecould fire multiple times under Fiber. Why, and why were they deprecated?
2. Reconciliation (Diffing Algorithm)
2. Reconciliation (Diffing Algorithm)
- Different element types produce different trees. If a
<div>becomes a<span>, React destroys the entire subtree and rebuilds. No attempt to reuse children. This sounds wasteful but in practice, cross-type morphing is rare and the simplification saves enormous complexity. - The
keyprop identifies stable elements across renders. Without keys, React matches children by index position. With keys, React can detect insertions, deletions, and moves.
- React walks both the current Fiber tree and the new element tree simultaneously.
- For each node, compares
typeandkey. - Same type + same key = update (reuse Fiber, apply new props).
- Different type = replace (delete old subtree, create new).
- Extra old children = deletion. Extra new children = placement.
- Changes are flagged as
effectTagon the Fiber node (e.g.,Placement,Update,Deletion).
key -> old Fiber, then iterates new children. For each, it looks up by key in O(1). This is why keys matter so much — without them, inserting at the top of a 1,000-item list causes React to update all 1,000 items (it thinks every item shifted).Real-World Impact: At a company rendering a dashboard with 500+ chart components, switching from index keys to stable IDs reduced reconciliation time from ~120ms to ~15ms on filter changes because React could correctly identify that most charts were unchanged.What interviewers are really testing: Do you understand the trade-offs React made (heuristic accuracy vs performance), and can you explain why keys exist at a mechanical level?Red flag answer: “React compares the real DOM to the virtual DOM.” No — React compares the previous virtual DOM (current Fiber tree) to the new virtual DOM (elements from render). The real DOM is only touched in the commit phase.Follow-up:- What happens if two sibling elements have the same key? What bug does this cause?
- Walk me through what happens when you insert an item at the beginning of a 100-item list with index keys vs stable ID keys.
- Why doesn’t React use a more sophisticated diffing algorithm? What would you gain and lose?
3. Virtual DOM works how?
3. Virtual DOM works how?
- Component
render()/ function body returns JSX, which Babel compiles toReact.createElement()calls (or the new JSX transform’sjsx()calls). createElementproduces plain objects:{ type: 'div', props: { className: 'app', children: [...] } }. This is the V-DOM.- React diffs the new V-DOM tree against the previous one (reconciliation).
- Produces a minimal list of “effects” — the actual DOM operations needed.
- Applies these in a single batch during the commit phase via
ReactDOM.
- DOM manipulation is not slow per se — layout thrashing is. Reading
offsetHeightafter writingstyle.heightforces a synchronous reflow. The V-DOM pattern naturally batches all writes together, avoiding read-write-read-write patterns. - The abstraction also enables React Native, React Three Fiber (3D), React PDF, etc. — same reconciler, different “renderers.”
- How does React’s V-DOM approach compare to Svelte’s compile-time approach or SolidJS’s fine-grained reactivity? When would each win?
- What is “layout thrashing” and how does batching DOM writes prevent it?
- If V-DOM diffing is O(n), at what tree size does this become a bottleneck, and what would you do about it?
4. React 18 Concurrency (Concurrent Features)
4. React 18 Concurrency (Concurrent Features)
createRoot (the new API). The key idea: not all state updates are equally urgent.Core Mechanism: React 18 uses a priority lane system (31 bit lanes). User interactions (clicks, typing) get high-priority lanes. Transitions get lower-priority lanes. React processes high-priority work first and can interrupt in-progress low-priority renders.Key Features:useTransition: Marks a state update as non-urgent. Returns[isPending, startTransition]. The UI stays responsive during the transition. Real example: Filtering a 10,000-row table — wrap the filter state update instartTransitionso typing remains smooth while the table re-renders in the background.
-
useDeferredValue: Creates a deferred copy of a value. Similar to debouncing but smarter — React controls the timing based on device capability. Fast devices see updates sooner. Use case: Showing a stale list while computing the new one. -
Automatic Batching: In React 17, batching only worked inside React event handlers. In React 18, updates inside
setTimeout,Promise.then(), native event handlers, and any other context are all batched. This was a major performance win — applications saw 20-40% fewer re-renders without code changes. -
Suspense for Data Fetching (SSR):
<Suspense>now works with SSR streaming. Server can send HTML for ready parts while slow data sources resolve. This is called Selective Hydration — React hydrates the most urgent components first (e.g., the one the user is trying to click).
ReactDOM.render() to createRoot(). Without this, you get “legacy mode” with React 17 behavior.What interviewers are really testing: Do you understand the practical impact of concurrency? Can you give a real scenario where useTransition matters? Do you understand the difference between concurrency and parallelism (React is still single-threaded)?Red flag answer: “React 18 makes everything concurrent automatically” or confusing concurrency with parallelism. React uses cooperative scheduling on a single thread — it does not use Web Workers or multi-threading.Follow-up:- What is the difference between
useTransitionanduseDeferredValue? When would you pick one over the other? - How does Automatic Batching in React 18 change error handling behavior compared to React 17? (Hint: errors in batched updates)
- Explain Selective Hydration. What happens if a user clicks on a component that has not been hydrated yet?
5. Synthetic Events
5. Synthetic Events
SyntheticEvent objects that provide a consistent, cross-browser interface. This normalizes differences across browsers (e.g., IE’s window.event pattern, differences in event.target behavior).Event Delegation: Instead of attaching an event listener to every single button/input, React attaches one listener per event type to the root container (#root in React 17+, previously document in React 16). When a native event fires, React:- Captures the native event at the root.
- Looks up which Fiber node corresponds to the
event.target. - Walks up the Fiber tree, collecting all relevant handlers (simulating bubbling through the React tree).
- Calls handlers in the correct order with a
SyntheticEvent.
document to the React root container. This was critical for micro-frontends — multiple React apps on one page no longer interfere with each other’s event systems. Before React 17, two React apps both listening on document could cause subtle bugs.Event Pooling (Removed in React 17): In React 16, SyntheticEvent objects were recycled for performance. After the event handler returned, all properties were nullified. Accessing event.target in a setTimeout would return null. You had to call event.persist(). React 17 removed this because modern JS engines made object creation cheap enough that pooling was no longer worth the developer confusion.Practical Implications:event.nativeEventgives you the underlying browser event if needed.event.stopPropagation()stops propagation in React’s synthetic system, not the native DOM (though React 17+ aligns these better).event.preventDefault()works as expected and delegates to the native event.- Passive event listeners (for scroll performance) require native
addEventListener— React does not support thepassiveoption on synthetic events.
- Why did React 17 move event delegation from
documentto the root container? What problem does this solve for micro-frontend architectures? - When would you need to use native
addEventListenerinstead of React’s synthetic events? Give a specific example. - How does React handle the capture phase vs bubbling phase? How do you add a capture-phase handler in React?
2. Hooks Deep Dive
6. Why Hooks cannot be conditional?
6. Why Hooks cannot be conditional?
memoizedState field. Each hook call appends to this list, and React identifies hooks purely by their call order (position in the list), not by any name or key.The Internal Mechanism:useEffect is the useState from last render (both at idx=0). It tries to apply useState logic to useEffect data — total corruption. You get either wrong state, wrong effects, or a crash.Why a Linked List Instead of a Map? You might think React could use hook names or keys. But hooks do not have names (two useState calls are indistinguishable). A Map keyed by something would add API overhead. The linked list approach is zero-overhead — O(1) per hook call, no hashing, no key management. The trade-off is the ordering constraint.The Linter Catches This: The eslint-plugin-react-hooks has a rule rules-of-hooks that statically analyzes your code to ensure hooks are not inside conditions, loops, or nested functions. This is a must-have lint rule, not optional. In a production codebase, violating this rule can cause bugs that are nearly impossible to debug because state silently shifts between hooks.What interviewers are really testing: Deep understanding of React’s internal data structures, not just rote memorization of “rules of hooks.”Red flag answer: “Because the docs say so” or “It’s a React rule” without being able to explain why at the implementation level.Follow-up:- Could React have designed hooks differently to allow conditional usage? What trade-offs would that involve?
- How does the
rules-of-hooksESLint plugin actually detect violations? Is it purely syntactic or does it do control flow analysis? - The new
usehook in React 19 can be called conditionally. How is this possible without breaking the linked list model?
7. `useEffect` vs `useLayoutEffect`
7. `useEffect` vs `useLayoutEffect`
useEffect:- Runs asynchronously after the browser has painted the screen.
- React schedules it via
requestIdleCallback/MessageChannel(notsetTimeout). - Users see the updated UI first, then effects run.
- Use for: Data fetching, analytics tracking, subscriptions, logging — anything that does not need to modify what the user sees.
useLayoutEffect:- Runs synchronously after DOM mutations but before the browser paints.
- Blocks the paint. The user never sees the “before” state.
- Use for: Reading DOM layout (measuring element dimensions with
getBoundingClientRect), adjusting positions, preventing visual flicker.
useLayoutEffect fires a warning during SSR because there is no DOM to measure on the server. Common fixes: use useEffect with an isMounted check, or use a library like useIsomorphicLayoutEffect that picks the right one based on environment.Performance Warning: Overusing useLayoutEffect blocks painting. If your effect takes 50ms (say, expensive computation), the user sees a 50ms freeze. Only use it when visual consistency requires it.What interviewers are really testing: Whether you understand the browser rendering pipeline (JS execution -> DOM mutation -> layout/paint) and can reason about when code runs relative to what the user sees.Red flag answer: “useLayoutEffect is the same as useEffect but runs first” without explaining why the timing matters or being able to give a concrete case where the difference is visible.Follow-up:- Draw the timeline: component renders, DOM updates,
useLayoutEffectruns, browser paints,useEffectruns. Where doesrequestAnimationFramefit? - What happens if you call
setStateinsideuseLayoutEffect? Does it cause a visible flicker? - In a Server Component world (RSC), how do layout effects work? What happens on the server?
8. `useMemo` vs `useCallback`
8. `useMemo` vs `useCallback`
useMemo: Caches a computed value. The factory function runs only when dependencies change.useCallback: Caches a function reference. Equivalent touseMemo(() => fn, deps).
useCallback Matters: In JavaScript, () => {} !== () => {} — every render creates a new function object. If you pass this to a child wrapped in React.memo, the child re-renders every time because its onClick prop changed (different reference). useCallback preserves the reference.When NOT to Use Them (Critical Nuance):- Memoization is not free.
useMemoadds: (1) dependency array comparison cost per render, (2) memory to store the cached value. For cheap computations (a + b), the memoization overhead exceeds the computation cost. A benchmark by the React team showed that unnecessaryuseMemocan make renders ~2-5% slower. - Do not wrap every function in
useCallback. Only do it when: (a) passed to a memoized child, (b) used as a dependency of another hook, or (c) the function creation itself is expensive.
useMemo/useCallback largely unnecessary. Existing code still works but new code can skip them.Real-World Pattern:- Can you implement
useCallbackusing onlyuseMemo? Show the code. - How does the React Compiler eliminate the need for manual memoization? What analysis does it perform?
- You have a component that re-renders 60 times per second (animation). Should you use
useMemofor computed values inside it? Why or why not?
9. `useRef` Internals
9. `useRef` Internals
useRef returns a mutable object { current: initialValue } that persists for the full lifetime of the component. The object reference itself never changes across renders.Three Key Properties:- Mutating
.currentdoes NOT trigger a re-render. This is fundamental —useRefis escape hatch from React’s reactivity system. - The ref object is the same instance across all renders. Unlike
useStatevalues which are snapshots,ref.currentalways reflects the latest mutation. - Synchronous access. You read/write
.currentimmediately, unlikesetStatewhich is batched/async.
- DOM access:
<input ref={inputRef} />theninputRef.current.focus(). - Storing mutable values without re-render: Interval IDs, previous props, animation frame IDs, WebSocket instances.
- Tracking “is mounted” (though React discourages this pattern):
useRef is essentially useMemo(() => ({ current: initialValue }), []). The initial value is computed once, and the same object is returned every render. But unlike useMemo, React guarantees the ref is never recreated (even if React’s cache is evicted).useRef vs createRef: createRef() creates a new ref object every render. It was designed for class components. In function components, always use useRef — createRef would lose the previous value on each render.Subtle Gotcha — Refs in Closures:- Why would you store a WebSocket connection in a ref instead of state? What would go wrong if you used state?
- Can you explain the “callback ref” pattern (
<div ref={node => ...} />)? When is it better thanuseRef? - In React 19, refs work as regular props (no
forwardRef). How does this simplify component APIs?
10. `useReducer` vs `useState`
10. `useReducer` vs `useState`
useState is internally implemented as a special case of useReducer (the reducer is essentially (state, action) => action for direct values or (state, action) => action(state) for functional updates).When to Pick useReducer:- Complex state transitions with multiple sub-values: A form with 10 fields, a data table with sort/filter/pagination state. With
useState, you end up with 10 separate setter calls in each handler. WithuseReducer, onedispatch({ type: 'SUBMIT' })handles everything. - Next state depends on previous state in complex ways: State machines, multi-step wizards, undo/redo.
- Testability: The reducer is a pure function — input state + action = output state. You can test it with zero React dependencies:
- Passing dispatch to deep children:
dispatchidentity is stable — it never changes across renders (unlike setter functions fromuseStatewhen not using functional updates). This means you can passdispatchto deeply nestedReact.memochildren without causing re-renders, and without needinguseCallback.
useState: Simple, independent values. A boolean toggle, a single input field, a counter. Using useReducer for const [isOpen, toggleOpen] is over-engineering.useState and useReducer are the same primitive underneath.Red flag answer: “useReducer is for complex state and useState is for simple state” without being able to explain why or articulate the specific benefits (testability, stable dispatch, collocated logic).Follow-up:- How would you implement an undo/redo system using
useReducer? Sketch the reducer. dispatchis guaranteed stable across renders. Why? How does React achieve this internally?- When would you combine
useReducerwithuseContext? What are the performance implications vs Redux?
11. Custom Hooks Pattern
11. Custom Hooks Pattern
use (this enables the linter to enforce Rules of Hooks). Each call to a custom hook gets its own isolated state — two components using useWindowSize() each get separate state.- No wrapper nesting hell: HOCs create
<WithAuth><WithTheme><WithRouter><Actual /></WithRouter></WithTheme></WithAuth>. Custom hooks are flat function calls. - No prop collision: Two HOCs might both inject
dataprop. Hooks return values you name yourself. - Composable: Hooks call other hooks.
useAuthenticatedFetchcan useuseAuth+useFetchinternally. - TypeScript friendly: HOC generic types are painful. Hook return types are simple.
useDebounce(value, delay)— debounce search inputuseLocalStorage(key, initialValue)— synced with localStorageuseMediaQuery('(max-width: 768px)')— responsive breakpointsuseIntersectionObserver(ref, options)— lazy loading triggersusePrevious(value)— access value from previous renderuseOnClickOutside(ref, handler)— close dropdowns
renderHook from @testing-library/react-hooks (or @testing-library/react v14+):- Design a
useAsync(asyncFn)hook that tracks loading, data, and error states. What edge cases would you handle? - How would you test a custom hook that depends on
window.addEventListener? Walk me through the test setup. - Can a custom hook return JSX? Should it? What pattern emerges if it does?
12. `useContext` + `useReducer` Pattern
12. `useContext` + `useReducer` Pattern
useReducer for state management — often called “poor man’s Redux.” It provides global state without external dependencies.Implementation:state and dispatch in one context value (value={{ state, dispatch }}), every consumer re-renders when state changes — even components that only use dispatch. By splitting them, components that only dispatch actions never re-render on state changes.The Fundamental Limitation — No Selectors: When state changes, ALL components consuming StateContext re-render, even if they only care about state.user and only state.cart changed. This is because Context uses reference equality on the value. A new state object (from the reducer spread) always has a new reference.Workarounds:- Split contexts by domain:
UserContext,CartContext,ThemeContext— each has its own provider. This limits re-render blast radius. useMemoin consumers: Compute derived values in the consumer withuseMemo, though the component still re-renders.use-context-selector(third-party): Adds selector support to context.- Just use Zustand/Jotai: If you need selectors, middleware, or devtools, the cost of a ~2KB library is worth it over fighting Context’s limitations.
- You have 50 components consuming a context, and state updates 10 times per second. What happens? How do you fix it?
- Compare the re-render behavior of Context vs Zustand’s
useStore(selector). Why is Zustand more efficient? - How would you add middleware-like functionality (logging, async actions) to the Context + Reducer pattern?
3. Component Patterns
13. Higher Order Components (HOC)
13. Higher Order Components (HOC)
connect() from React-Redux, withRouter from React Router v5, withStyles from Material UI v4.The Problems That Led to Hooks:- Wrapper Hell:
withAuth(withTheme(withRouter(withAnalytics(Component))))creates deeply nested component trees. React DevTools shows 8 layers of wrappers. Debugging becomes painful. - Prop Name Collisions: If
withAuthinjectsdataandwithFetchalso injectsdata, the last HOC wins and the first one’s prop is silently lost. - Static Composition Only: HOCs are applied at definition time, not render time. You cannot dynamically decide which HOCs to apply based on props.
- TypeScript Pain: Properly typing the input component, the injected props, and the output component requires complex generics that many teams get wrong.
- Ref Forwarding: HOCs need explicit
React.forwardRefto pass refs through to the wrapped component.
- How would you convert an existing HOC (
withAuth) into a custom hook (useAuth)? What changes in the consumer code? - What is the “wrapper hell” problem and how does it affect React DevTools debugging?
- How do you ensure an HOC properly forwards refs? Show the code.
14. Render Props
14. Render Props
children as a Function (more common):shouldComponentUpdate / React.memo on the component receiving the render prop. The fix is to extract the render function:<Spring>{styles => ...}</Spring>), Formik (<Field>{(field) => ...}</Field>), and Downshift.What interviewers are really testing: Understanding of inversion of control as a design principle, and the evolution from render props to hooks.Red flag answer: Not knowing the children as function variant, or unable to articulate any downsides (nesting, performance).Follow-up:- Convert this render props component to a custom hook. What is gained and lost?
- How would you handle the case where the render prop function is expensive to execute? What optimization strategies exist?
- Can you combine render props with hooks? Give a scenario where this makes sense.
15. Compound Components
15. Compound Components
<select> and <option> — they communicate internally without you wiring them up.Implementation with Context:React.Children.map+cloneElement: The older approach. Parent iterates children and injects props viacloneElement. Brittle — breaks if you wrap children in a<div>.- Context-based (modern, preferred): Parent provides context, children consume it. Works regardless of DOM nesting depth. More flexible.
<Accordion>, <Tabs>, <Menu> components.What interviewers are really testing: Component API design skills. Can you design APIs that are intuitive for consumers? Do you understand implicit vs explicit state sharing?Red flag answer: Only knowing the cloneElement approach, or not being able to articulate why compound components are better than passing everything as props to a single component.Follow-up:- What are the trade-offs between
cloneElement-based vs Context-based compound components? - How would you make compound components type-safe with TypeScript so that
Tabs.Tabcannot be used outsideTabs? - How do headless component libraries (Radix, Headless UI) use compound components to separate logic from styling?
16. Controlled vs Uncontrolled
16. Controlled vs Uncontrolled
-
Controlled: React is the single source of truth. The component’s value is driven by state (
value={state}), and changes flow through anonChangehandler that callssetState. Every keystroke goes: DOM event -> handler ->setState-> re-render -> DOM updated. -
Uncontrolled: The DOM itself holds the state. You use
defaultValueto set the initial value and arefto read the current value when needed (e.g., on form submit). React does not track intermediate changes.
- Controlled: When you need real-time validation, conditional disabling, formatting-as-you-type (phone numbers, credit cards), or when multiple components need to stay in sync with the same value.
- Uncontrolled: Simple forms where you only need values on submit, integrating with non-React libraries (e.g., a jQuery datepicker), or file inputs (
<input type="file" />is always uncontrolled — React cannot set file values for security reasons).
useDeferredValue, or (3) use a form library like React Hook Form which uses uncontrolled inputs by default with refs for performance.React Hook Form vs Formik: This is the controlled vs uncontrolled debate in library form. Formik uses controlled components (re-renders on every change). React Hook Form uses uncontrolled with refs (minimal re-renders). On forms with 50+ fields, React Hook Form can be 5-10x more performant in terms of re-renders.What interviewers are really testing: Understanding of data flow, performance implications of each approach, and practical judgment about when to use which.Red flag answer: “Always use controlled components because they’re the React way.” This ignores valid use cases for uncontrolled components and performance trade-offs.Follow-up:- You have a form with 100 fields and users report input lag. How would you diagnose and fix this?
- What happens if you set
valueon an input without anonChangehandler? What does React do? - How does React Hook Form achieve better performance than Formik? What is the architectural difference?
17. Error Boundaries
17. Error Boundaries
- Event handlers — use regular try/catch. Event handler errors do not corrupt the render tree.
- Async code (setTimeout, Promises) — errors happen outside React’s call stack.
- Server-side rendering — different error handling mechanism.
- Errors in the boundary itself — only catches errors in children, not in itself.
- Route-level: Each page gets its own boundary. A crash in
/settingsdoes not break/dashboard. - Feature-level: A failing chart widget does not take down the sidebar.
- Critical vs non-critical: Show a generic error for the chat widget but show a full-page error for the main content.
react-error-boundary by Brian Vaughn provide this with hooks support and retry functionality.What interviewers are really testing: Error handling strategy and resilience thinking. Where do you place boundaries? How do you handle recovery? Do you know the limitations?Red flag answer: “Error boundaries catch all errors in React” or placing a single boundary at the root without considering granularity.Follow-up:- Why is there no hook-based Error Boundary? What technical limitation prevents it?
- How would you implement a “retry” mechanism in an error boundary? What state needs to reset?
- How do Error Boundaries interact with Suspense boundaries? Can you combine them?
18. Portals
18. Portals
overflow: hidden, z-index stacking contexts, and position constraints. A modal inside a <div style="overflow: hidden"> gets clipped. A tooltip inside a scrolling container scrolls with it. Portals break out of these CSS constraints by rendering into a different DOM location.The Magic — Event Bubbling Through Portals: This is the most commonly missed detail. Even though the portal renders into #modal-root (outside the parent in the DOM), events still bubble up through the React tree (virtual hierarchy). A click inside a portal still triggers onClick on the React parent component:- Modals and dialogs
- Tooltips and popovers
- Toast notifications
- Dropdown menus that need to escape
overflow: hidden - Full-screen overlays
aria-modal="true" and proper role="dialog". Libraries like Radix UI and Headless UI handle this automatically.What interviewers are really testing: Understanding of the DOM vs React tree distinction, CSS stacking context issues, and awareness of accessibility implications.Red flag answer: Not knowing that events bubble through the React tree (not DOM tree) for portals, or not mentioning accessibility/focus management.Follow-up:- Explain a scenario where event bubbling through a portal could cause a bug. How would you handle it?
- How do you handle keyboard focus trapping inside a modal rendered via a portal?
- Can you create a portal to an element inside an iframe? What challenges arise?
4. Performance Optimization
19. `React.memo`
19. `React.memo`
React.memo is a higher-order component that memoizes functional components. It performs a shallow comparison of props — if all props are equal by reference (or value for primitives), the component skips re-rendering and reuses the previous render output.React.memo is rendered useless if the parent creates new objects/functions every render:useMemo for objects and useCallback for functions. Or better, restructure to pass primitives:React.memo, establish a baseline. After adding it, verify the component actually skips renders. At a dashboard with 200+ widgets, strategically applying React.memo to widget containers reduced average frame time from 33ms to 8ms during filter operations.What interviewers are really testing: Do you understand reference equality in JavaScript? Can you identify the real performance bottleneck before reaching for memo?Red flag answer: “I wrap every component in React.memo for performance” — shows premature optimization without understanding the overhead and the reference stability requirement.Follow-up:- If
React.memouses shallow comparison, what exactly does “shallow” mean? How deep does it go? - When would you write a custom comparison function for
React.memo? What are the risks? - How does the React Compiler in React 19 change the need for
React.memo?
20. Code Splitting (Lazy & Suspense)
20. Code Splitting (Lazy & Suspense)
React.lazy and Suspense as first-class APIs for this.React.lazytakes a function that returns aPromise<{ default: Component }>(the dynamicimport()spec).- On first render, the component is not loaded. React “suspends” — throws a Promise.
- The nearest
Suspenseboundary catches the Promise and shows thefallback. - When the chunk loads (Promise resolves), React re-renders with the real component.
- Route-level splitting: Each page is a separate chunk. Most impactful because users only load the page they visit. Can reduce initial bundle from 2MB to 200KB.
- Heavy library splitting: Import
chart.jsormonaco-editoronly when the chart/editor component mounts. - Below-the-fold splitting: Content not visible on initial load does not need to be in the main bundle.
- Modal/dialog splitting: Admin settings modals loaded only when opened.
Suspense with an Error Boundary to handle chunk load failures (network errors):webpack-bundle-analyzer or vite-plugin-visualizer to identify which chunks are large and what dependencies they pull in. A common finding: moment.js (300KB) pulled into the main bundle because one component uses it.What interviewers are really testing: Do you think about performance holistically? Can you identify the right splitting boundaries? Do you handle failure cases?Red flag answer: Only knowing the syntax without understanding splitting strategy, prefetching, or error handling for failed chunk loads.Follow-up:- How would you implement a “retry on failure” mechanism for a failed lazy-loaded chunk?
- What is the difference between
React.lazyand dynamicimport()with manual state management? When would you choose one over the other? - How does React Suspense for data fetching differ from Suspense for code splitting?
21. Excessive Re-renders Fix
21. Excessive Re-renders Fix
-
React DevTools Profiler (first tool, always): Enable “Record why each component rendered.” Run the Profiler, perform the slow interaction, stop recording. Look for:
- Components rendering many times (high render count).
- Components with long render durations.
- The “Why did this render?” column — shows “Props changed”, “State changed”, “Parent rendered”, or “Hooks changed.”
-
console.logoruseWhyDidYouRender: For quick debugging, add a log at the top of the component. Thewhy-did-you-renderlibrary patches React to log exactly which prop/state change triggered the re-render. -
Common Root Causes and Fixes:
Cause: Context value changes too often.
Fix: Split context, memoize the value, or move to Zustand/Jotai for high-frequency state.
Cause: Parent passes new object/function references.
Fix:
useMemofor objects,useCallbackfor functions, or restructure to pass primitives. Cause: State too high in the tree. Fix: Move state down to the component that actually needs it (composition pattern / “lift state down”). Cause: Subscribing to an entire store instead of a slice. Fix: Use selectors (useStore(state => state.count)in Zustand). Cause:useEffectdependency array creates a loop (effect updates state that is in its own deps). Fix: Use functional state updates or extract the logic. - Nuclear Option — Composition Refactor: Sometimes the component tree structure itself is the problem. Splitting a “god component” into smaller, focused components with clear data boundaries can eliminate 80% of unnecessary re-renders.
columns config array was recreated every render in the parent. Fix: useMemo on the columns array. Result: sort operation went from 400ms to 12ms.What interviewers are really testing: Systematic debugging methodology. Do you reach for the right tools? Can you identify root causes vs symptoms?Red flag answer: “Just wrap everything in React.memo and useMemo” — this is a shotgun approach that shows no diagnostic skill.Follow-up:- Walk me through how you would profile a specific slow interaction step by step using React DevTools.
- What is the “composition pattern” for avoiding re-renders, and how does moving state down help?
- How do you distinguish between re-renders that are expensive (need fixing) vs re-renders that are cheap (acceptable)?
22. List Virtualization
22. List Virtualization
<div> elements, each taking ~1KB of memory and requiring layout calculation. Virtualization solves this by rendering only the visible slice of the list (typically 20-50 items) plus a small buffer, and dynamically swapping elements as the user scrolls.How It Works Internally:- Calculate which items are visible based on scroll position and container height.
- Render only those items, absolutely positioned at their correct offset.
- Set the total container height to simulate the full list (so the scrollbar looks correct).
- On scroll, recalculate visible range and swap items.
react-window(Brian Vaughn, React core team): Lightweight (~6KB), covers 90% of use cases. Fixed and variable size lists/grids.react-virtuoso: More features out of the box (grouped lists, reverse scrolling for chat, table support).@tanstack/react-virtual: Headless (no DOM opinions), framework-agnostic, great TypeScript support.
VariableSizeList with a size estimator function. You often need to measure items after render and update the cache, which react-virtuoso handles better out of the box.Gotchas:- Search/Find on page (Ctrl+F): Browser search cannot find text in non-rendered items. Workaround: add invisible text or use a custom search UI.
- Accessibility: Screen readers may not announce total list size. Add
aria-rowcountandaria-rowindex. - Scroll restoration: Navigating away and back loses scroll position. Store scroll offset and restore on mount.
- Dynamic content loading: Combine virtualization with infinite scroll (load more data as user approaches the bottom).
- How would you implement virtualization for a grid (2D) vs a list (1D)? What additional complexity arises?
- At what item count does virtualization become worthwhile? How would you benchmark this?
- How do you handle items with unknown/dynamic heights in a virtualized list?
23. Key Prop anti-pattern
23. Key Prop anti-pattern
index as a key is one of the most common React anti-patterns, and it causes silent, hard-to-debug state corruption in specific scenarios.Why Index Keys Break:
When you use key={index}, React identifies elements by their position. If the list is reordered, filtered, or has items inserted/deleted at the beginning or middle:- React sees
key=0still exists,key=1still exists, etc. - It reuses the Fiber nodes (and their state) for those keys.
- But the data at each position has changed.
- Result: Component at position 0 shows the state of the old item 0 (input values, selection state, animation state) but the props of the new item 0.
item.id from the database. If no ID exists, generate one when the data is created (not during render — uuid() in render creates new keys every render, which destroys all component instances every time):- Give me a scenario where using
indexas key causes a visible bug. Walk through the reconciliation step by step. - What happens if two siblings have the same key? Does React crash or silently misbehave?
- Can you use the
keyprop to intentionally reset a component’s state? When is this useful?
24. Immutable State Why?
24. Immutable State Why?
===). When you call setState, React compares the new value to the old value. For primitives (numbers, strings), this checks the value. For objects and arrays, this checks the memory reference.The Core Problem:- Deep comparison is O(n) where n is the total size of the object tree. For a Redux store with 10,000 entries, deep comparing on every update would be more expensive than just re-rendering.
- Reference equality is O(1) — a single pointer comparison.
- The trade-off: developers must create new references when data changes. This is the “immutable update” pattern.
- Time-travel debugging (Redux DevTools): Each state snapshot is a separate object, so you can replay them.
- Structural sharing: Libraries like Immer only clone the changed branches, reusing unchanged sub-trees. This is memory-efficient.
- Predictable state: No spooky action at a distance. If you hold a reference to the old state, it never changes.
createSlice reducers, which is why you can write “mutating” code in RTK reducers.What interviewers are really testing: Understanding of JavaScript reference semantics, why React made this design choice, and practical strategies for working with immutable data.Red flag answer: “Because React requires immutability” without explaining why (reference equality) or knowing about tools like Immer that make it ergonomic.Follow-up:- How does Immer achieve “mutable syntax, immutable results” under the hood? (Hint: Proxy)
- What is structural sharing and why does it matter for performance when dealing with large state trees?
- How do signals-based frameworks (SolidJS, Angular Signals) handle reactivity differently than React’s immutable reference model?
5. Ecosystem & Advanced
25. Redux vs Context
25. Redux vs Context
- Purpose: Dependency injection. Passing values down without prop drilling.
- Best for: Static or low-frequency global data — theme, locale, auth status, feature flags.
- Re-render behavior: When the context value changes, every component calling
useContext(ThatContext)re-renders, regardless of whether they use the specific piece that changed. No selector mechanism. - Async: No built-in support. You handle loading/error states yourself.
- Devtools: React DevTools shows context values but no action history or time travel.
- Purpose: Predictable state management with fine-grained subscriptions.
- Best for: High-frequency updates, complex state logic, state shared across many components with different access patterns.
- Re-render behavior: Selectors (
useSelector,useStore(s => s.count)) let components subscribe to specific slices. Only re-renders when the selected value changes. - Middleware: Thunks, sagas, logging, persistence, optimistic updates.
- Devtools: Full action history, time-travel debugging, state diff visualization.
- How often does the data change? Once per session (auth, theme) -> Context. Multiple times per second (form input, real-time data) -> External library.
- How many components consume it? A few -> Context is fine. Dozens with different access patterns -> Need selectors.
- Do you need middleware? Async flows, logging, persistence -> External library.
- Team size and complexity? 2-person project -> Context + useReducer. 20-person team with complex state -> Redux/Zustand for the dev tools and predictability.
- Design a scenario where Context causes a performance problem. How would you detect and fix it?
- Compare Zustand, Jotai, and Redux Toolkit. When would you pick each one?
- How does TanStack Query change the “which state manager?” question? What state does it handle that Redux/Context should not?
26. SSR (Server Side Rendering) vs CSR
26. SSR (Server Side Rendering) vs CSR
- Server sends a nearly empty HTML file with a
<script>tag. - Browser downloads JS bundle (often 500KB-2MB), parses, executes.
- React mounts and renders the entire UI.
- FCP (First Contentful Paint): Slow (2-5s on average connections). User stares at a white screen.
- TTI (Time to Interactive): Same as FCP since JS is already loaded.
- SEO: Bad — Google crawler sees empty HTML (though Googlebot does execute JS now, it is slower and less reliable).
- Use case: Internal dashboards, authenticated apps where SEO does not matter.
- Server runs React, generates full HTML string, sends it.
- Browser renders HTML immediately (fast FCP, 200-500ms).
- JS bundle downloads in parallel.
- Hydration: React “attaches” event listeners to the existing server-rendered HTML. During hydration, React reconstructs the component tree and binds interactivity.
- TTI: Slower than FCP — user sees content but cannot interact until hydration completes.
- SEO: Excellent — crawler gets full HTML.
getStaticProps, Astro, Gatsby.Hydration Errors — The #1 SSR Bug: If the server-rendered HTML does not match what the client renders, React throws a hydration mismatch error. Common causes:Date.now()orMath.random()in render (different on server vs client)window/documentaccess during render (does not exist on server)- Browser extensions injecting attributes
- Fix: Use
useEffectfor client-only logic (it only runs on the client).
<Suspense>, slow data sources do not block the fast parts. The user sees progressive content. This is a major improvement over blocking SSR.What interviewers are really testing: Understanding of the full rendering pipeline, performance metrics (FCP vs TTI), and practical experience with SSR challenges (hydration errors, server/client mismatch).Red flag answer: “SSR is always better than CSR” or not knowing about hydration errors.Follow-up:- Explain the “uncanny valley” problem in SSR — the page looks ready but is not interactive. How does React 18 Streaming mitigate this?
- What is Selective Hydration in React 18? How does it decide which components to hydrate first?
- Compare Next.js App Router (RSC + streaming) vs Pages Router (traditional SSR). What are the trade-offs?
27. React Server Components (RSC)
27. React Server Components (RSC)
- Zero bundle size: RSC code is not included in the client JavaScript bundle. A component importing a 500KB Markdown parser adds 0 bytes to the client bundle.
- Direct backend access: Can query databases, read files, call internal microservices directly in the component body.
- No hooks, no state, no event listeners: RSCs are purely for rendering. They are essentially server-side templates with full React composition.
- Serializable output: RSCs produce a serialized React tree (RSC payload) that is streamed to the client, not HTML. The client React runtime reconstructs the tree.
- What can you NOT pass from a Server Component to a Client Component as props? (Hint: functions, classes, non-serializable values)
- How does RSC handle data fetching compared to
useEffect+ loading states? What is the waterfall problem it solves? - What is the difference between
'use server'and'use client'directives? Can a Server Component import a Client Component and vice versa?
28. Testing (RTL vs Enzyme)
28. Testing (RTL vs Enzyme)
- Tests implementation details: checks state values, instance methods, component structure.
wrapper.state('count')— directly reads component state.wrapper.instance().handleClick()— calls methods on the instance.- Problem: Refactoring the component (changing state names, extracting hooks, restructuring) breaks tests even if behavior is identical. Tests become a refactoring tax.
- Tests user behavior: what the user sees and interacts with.
- Queries by role, text, label — the way a user finds elements.
screen.getByRole('button', { name: 'Submit' })— finds the submit button.userEvent.click(button)— simulates real user interaction.- Principle: “The more your tests resemble the way your software is used, the more confidence they can give you.”
- Static analysis (TypeScript, ESLint): Catch typos, type errors, unused imports. Free, fast.
- Unit tests: Pure functions, reducers, utils. Fast, focused.
- Integration tests (RTL): Render a component with its children, test user flows. This is where most value lies.
- E2E tests (Cypress, Playwright): Full browser, real API. Slow but catches integration issues.
- How do you test a component that fetches data from an API? Walk me through the full test setup with MSW.
- What is the difference between
fireEvent.clickanduserEvent.clickin RTL? When does it matter? - How would you test a component that uses Context? What about one that uses Redux?
29. Strict Mode effects
29. Strict Mode effects
<React.StrictMode> is a development-only tool that enables additional checks and warnings. It has no effect in production builds — it is stripped out entirely.What It Does (React 18):- Double-invokes render functions: Calls your component function twice per render (discarding the first result). This catches impure renders — if your component produces different output on second call (because it mutates external state during render), StrictMode exposes it.
-
Double-invokes effects (Mount -> Unmount -> Mount): Calls
useEffectsetup, then cleanup, then setup again. This tests whether your cleanup logic is correct. Real-world implication: If your effect opens a WebSocket connection, StrictMode verifies that the cleanup closes it and the re-setup opens it again without issues. Without StrictMode, a missing cleanup would only manifest when the component actually unmounts (often in production, under specific navigation patterns). -
Warns about deprecated APIs:
findDOMNode,UNSAFE_componentWillMount, string refs, legacy context API. - Detects unexpected side effects: Since the render phase in Concurrent Mode can be interrupted and restarted, any side effects in render are bugs. Double invocation catches these.
- StrictMode double-renders in development but not production. Could this mask a bug that only appears in production?
- How does StrictMode’s double-effect behavior help prepare for features like Offscreen (activity) in future React versions?
- What specific class component lifecycles does StrictMode intentionally double-invoke, and why?
30. Prop Drilling Solutions
30. Prop Drilling Solutions
-
Component Composition (often overlooked, most underrated):
Instead of drilling props through intermediaries, pass the entire component as a prop. The key insight: if
Layoutdoes not needuser, do not make it passuserthrough:This is the “inversion of control” pattern. Components higher in the tree decide what to render, passing assembled components down. The intermediary components are just containers. - Context API: For truly global data accessed by many components at different tree depths (theme, auth, locale). Use sparingly — Context is not a general state management tool.
- State Management Library (Zustand/Jotai/Redux): When you have complex global state with multiple consumers needing different slices. The “store” is accessible from anywhere without threading through the tree.
-
URL State: Often overlooked. For state that should persist across navigation or be shareable (filters, sort, pagination), use URL params. Libraries:
nuqs, React Router’suseSearchParams.
- Refactor a 5-level prop drilling chain using the composition pattern. Show before and after.
- When is prop drilling actually preferable to Context? Give a specific scenario.
- How does the “slots” pattern (named children) help with prop drilling in design systems?
6. Coding Scenarios
31. Implement a Counter (Hooks)
31. Implement a Counter (Hooks)
c => c + 1) Matters: If you call setCount(count + 1) three times in the same event handler, you get count + 1 (not count + 3) because count is a stale closure value from the current render. With c => c + 1, each call gets the latest pending state.setCount(count + 1) and not knowing why it might cause bugs with rapid updates or multiple calls.Follow-up:- What happens if you call
setCount(count + 1)inside asetTimeout? How does React 18’s automatic batching change this? - How would you add a “step” feature where the increment amount is configurable? Should
stepbe state or a prop? - Add keyboard shortcuts (arrow keys) to this counter. What hook do you use and how do you handle cleanup?
32. Fetch Data with useEffect
32. Fetch Data with useEffect
useEffect requires handling the race condition where a component unmounts or the dependency changes before the fetch completes.cancelled flag, setUser sets profile A’s data even though we are now looking at profile B.Modern Alternative — AbortController:AbortController is better than the boolean flag because it actually cancels the in-flight HTTP request, saving bandwidth and server resources.Why Not Just Use TanStack Query?: In production, you almost always should. TanStack Query (React Query) handles caching, deduplication, retry, background refetch, stale-while-revalidate, pagination, infinite scroll, and optimistic updates. The useEffect + fetch pattern is educational but lacks production resilience.What interviewers are really testing: Do you handle race conditions? Do you know about AbortController? Are you aware that useEffect for data fetching is a solved problem (use a library)?Red flag answer: No cleanup function, no race condition handling, no loading/error states.Follow-up:- What is the difference between the boolean cancellation flag and
AbortController? When does the difference matter? - How would you add caching to avoid refetching data the user already viewed? Sketch the approach.
- React docs now recommend against
useEffectfor data fetching. What do they recommend instead, and why?
33. Implement `usePrevious` Hook
33. Implement `usePrevious` Hook
useRef mutations (which persist immediately) and useEffect (which runs after render).- Component renders with
price = 100.ref.currentisundefined(initial). - After render,
useEffectruns:ref.current = 100. - Next render with
price = 105.usePreviousreturnsref.currentwhich is100(the old value). - After this render,
useEffectupdatesref.current = 105.
prevPrice is undefined on the first render. Handle this in the consumer: if (prevPrice === undefined) return;.Alternative without useEffect (updates synchronously):useEffect vs render, and how useRef enables persistence without re-render.Red flag answer: Using useState instead of useRef (would cause an infinite render loop), or not understanding why the value is “previous.”Follow-up:- Why does this hook use
useRefinstead ofuseState? What would happen withuseState? - Modify this hook to track the last N values (e.g.,
usePreviousN(value, 5)returns the last 5 values). - When would you use
usePreviousin production? Give a real-world example beyond price comparison.
34. Implement `useDebounce` Hook
34. Implement `useDebounce` Hook
- User types “a” ->
value= “a” -> timer set for 300ms. - User types “ab” (within 300ms) -> cleanup clears old timer -> new timer set for 300ms.
- User types “abc” (within 300ms) -> cleanup clears old timer -> new timer set for 300ms.
- User stops typing -> 300ms passes ->
setDebouncedValue("abc")fires. - Consumer effect sees
debouncedQuerychange -> fires API call once.
useDeferredValue: Instead of debouncing with a fixed delay, useDeferredValue lets React decide when to update based on device capability. Fast devices see updates sooner. It also integrates with Concurrent features (interruptible rendering).What interviewers are really testing: Understanding of timers, cleanup, and the performance implications of rapid-fire updates. Also: do you know when useDeferredValue is a better choice?Red flag answer: Not including the cleanup function (leads to memory leaks and stale updates), or not knowing about useDeferredValue as a modern alternative.Follow-up:- Implement a
useThrottlehook. How does the implementation differ fromuseDebounce? - When would
useDeferredValuebe better thanuseDebounce? When would it not? - How would you add a “leading edge” option to the debounce (fire immediately on first call, then debounce subsequent calls)?
35. Optimize a heavy list (React.memo)
35. Optimize a heavy list (React.memo)
React.memoonItem: Skips re-render when props have not changed.useCallbackon handlers: Without this,onDeleteandonToggleare new functions every render, defeatingReact.memo.- Functional state updates (
prev => ...): AllowsuseCallbackto have[]deps (no dependency onitems), keeping the reference stable. useMemoon filtered list: Avoids re-filtering 10,000 items on every render that does not changeitemsorfilter.key={item.id}: Stable keys prevent unnecessary unmount/remount cycles.
React.memo, if item objects are recreated upstream (e.g., the parent maps over data and creates new objects), every child re-renders. Ensure the items array and its individual objects maintain reference stability where possible.What interviewers are really testing: Can you build a complete, production-quality optimized list? Do you understand the chain of reference stability from parent to child?Red flag answer: Only applying React.memo without useCallback/useMemo, which means the memo has zero effect.Follow-up:- What if
handleDeleteneeded to accessfilterstate? How would you keep the callback stable while depending on state? - At 50,000 items, even the optimized version would be slow. What would you do next? (Hint: virtualization)
- How would you verify that your optimizations are actually working? Walk me through the profiling process.
36. Force Update (Hack)
36. Force Update (Hack)
x + 1), so React always sees a state change and re-renders. The 0 initial state is meaningless.When You Might Actually Need This:- Integrating with non-React libraries that mutate external data (e.g., a canvas library, a legacy jQuery component).
- Using
useRefto store mutable state that you occasionally want to “sync” to the UI. - Quick prototyping or debugging.
useState or useReducer so React knows about it. Force update breaks the predictability of React’s rendering model.Class Component Equivalent: this.forceUpdate() — built-in method but equally discouraged.What interviewers are really testing: Do you know this trick? More importantly, do you understand why it is usually wrong?Red flag answer: Using this pattern regularly or recommending it without explaining that it indicates an architectural issue.Follow-up:- Name a real scenario where
forceUpdateis justified. Now, can you redesign it to not needforceUpdate? - What is
useSyncExternalStoreand how does it replace the need forforceUpdatewhen subscribing to external stores? - Does calling
forceUpdatecause child components to re-render? How does this interact withReact.memo?
37. Modal with Portal
37. Modal with Portal
- Focus trap: When modal opens, focus moves to the first focusable element. Tab cycles within the modal. On close, focus returns to the trigger button. Libraries:
focus-trap-react. - Animation:
framer-motionfor enter/exit animations. Requires delaying unmount until exit animation completes. - Stacking: If multiple modals can open (confirmation dialog over a settings modal), manage z-index and focus trap nesting.
- SSR safety: Check
typeof document !== 'undefined'before usingdocument.getElementById.
- How would you implement focus trapping without a library? Walk through the logic.
- What is the accessibility implication of
aria-modal="true"and how does it differ fromrole="dialog"? - How would you add enter/exit animations to this modal without unmounting before the animation completes?
38. Focus Input on Mount
38. Focus Input on Mount
autoFocus Prop:autoFocus prop which calls element.focus() after mount. However, it has limitations: it does not work reliably inside portals, dynamically rendered content, or when the element is inside a <Suspense> boundary that resolves later.Callback Ref Pattern (Most Robust):useEffect + useRef for timing-sensitive operations.Accessibility Note: Auto-focus can disorient screen reader users by moving focus unexpectedly. Use it judiciously — primarily for primary actions in newly opened dialogs or dedicated search pages, not for every form field on the page.What interviewers are really testing: Do you know multiple approaches (useRef + useEffect, autoFocus prop, callback ref) and their trade-offs?Red flag answer: Only knowing the useRef approach or using autoFocus without knowing its limitations.Follow-up:- What is the difference between
useRef+useEffectand a callback ref for DOM operations? When is the callback ref better? - How would you focus the second input in a form when the first one is already filled? Show the logic.
- What accessibility considerations should you account for when auto-focusing elements?
39. Context API basic implementation
39. Context API basic implementation
- Default value:
createContext('light')— this value is used only when there is no Provider above the consumer in the tree. Common for testing. - Value identity: If the Provider’s
valueprop is a new object every render (value={{ theme, user }}), all consumers re-render every time the Provider renders, even ifthemeanduserhave not changed. Fix:useMemoon the value or split into separate contexts. - Multiple contexts: A component can consume multiple contexts (
useContext(ThemeCtx),useContext(AuthCtx)). Each is independent. - Nesting: Providers can be nested. The nearest ancestor Provider wins for a given context type.
value causes all consumers to re-render, or not knowing about the default value behavior.Follow-up:- What happens if you consume a context without any Provider ancestor? What value do you get?
- How would you prevent re-renders when only one part of the context value changes?
- In React 19, you can use
<ThemeContext value="dark">instead of<ThemeContext.Provider value="dark">. What else changed about Context in React 19?
40. Catch 404 in Router
40. Catch 404 in Router
path="*" catch-all route to handle unmatched URLs:<Redirect> (that is React Router v5 API) or not knowing about nested catch-all routes.Follow-up:- How would you handle a 404 within a data loader (the URL matches but the resource does not exist in the database)?
- What is the difference between
errorElementand a catch-all*route in React Router v6? - How do you implement authenticated routes that redirect to login if the user is not logged in?
7. Edge Cases & Trivia
41. Can you use Hooks in Class Components?
41. Can you use Hooks in Class Components?
memoizedState linked list, which is only populated when React calls a function component. Class components use a different internal mechanism (instance-based state).Workaround — Wrapper Component:- Why did React not add hook support to class components? What would be the technical challenge?
- What is your strategy for migrating a large codebase from class to function components? Do you do it all at once?
- Are there any class component features that hooks cannot replicate? (Hint:
getSnapshotBeforeUpdate, Error Boundaries)
42. What happens if you call setState in render?
42. What happens if you call setState in render?
setState (or setCount, etc.) directly in the render body (not inside an event handler, effect, or callback) causes an infinite render loop:getDerivedStateFromProps lifecycle. React handles it by immediately re-rendering with the new state, without committing the intermediate state to the DOM. But you must guard it with a condition to prevent the loop.What interviewers are really testing: Understanding of React’s render cycle and the subtle exception for derived state.Red flag answer: Only saying “infinite loop” without knowing about the conditional exception, or conversely, using the conditional pattern without understanding that it causes an extra render.Follow-up:- How does React detect the infinite loop? What is the threshold before it throws?
- Explain the “derived state during render” pattern. How is it different from
useEffectfor the same purpose? - What about calling
setStateinsideuseLayoutEffect— does that cause a visible flicker?
43. Is `setState` async?
43. Is `setState` async?
setState is not truly async (it does not return a Promise). It is batched. React collects multiple state updates and processes them together in a single re-render, rather than re-rendering after each setState call.React 18 Behavior (Automatic Batching): ALL updates are batched, regardless of where they originate:setTimeout, Promise.then, and native event handlers triggered separate re-renders for each setState.Opting Out of Batching (rare): Use flushSync to force immediate processing:setState:- What is
flushSyncand when would you use it? Give a real scenario. - How does automatic batching in React 18 affect error handling? If the first
setStatein a batch throws, what happens to the rest? - In class components,
setStatehas a callback parameter. What is the equivalent in function components?
44. `super(props)` purpose
44. `super(props)` purpose
super(props) calls the parent React.Component constructor with the component’s props, which stores them as this.props.super(props):- Calling
super()(without props) meansthis.propsisundefinedinside the constructor. It will work correctly inrender()and other methods because React assignsthis.propsexternally after construction. - Not calling
super()at all causes a ReferenceError — you cannot usethisbefore callingsuper()in a derived class (ES6 specification requirement).
- Why can you access
this.propsinrender()even if you calledsuper()without props? - What did
super(props)have to do with the oldercreateReactClassAPI? - This is a JavaScript/ES6 question at its core. Explain
super()in the context of general class inheritance.
45. Why does React require a Root element?
45. Why does React require a Root element?
React.createElement returns a single object. Multiple adjacent elements would need to return multiple objects, which was not supported until Fragments.Fragment (<>...</>) solves this by providing a virtual wrapper that does not create a real DOM node:<div> wrappers break CSS Flexbox and Grid layouts. If a parent is display: flex and you add a wrapping <div> around flex children, the layout breaks because the <div> becomes the single flex child instead of the intended multiple children.What interviewers are really testing: Understanding of React’s tree model and the practical implications of unnecessary DOM nodes.Red flag answer: Not knowing about React.Fragment or when to use the keyed version.Follow-up:- When must you use
<React.Fragment key={...}>instead of the shorthand<>...</>? - How do unnecessary wrapper divs affect CSS layouts, accessibility, and performance?
- Some frameworks (Svelte, SolidJS) allow returning multiple root elements. How do they handle this differently?
46. React vs Angular vs Vue
46. React vs Angular vs Vue
| Aspect | React | Angular | Vue |
|---|---|---|---|
| Type | Library (UI layer only) | Full framework (batteries included) | Progressive framework |
| DOM Strategy | Virtual DOM + reconciliation | Incremental DOM (Ivy renderer) + change detection | Virtual DOM + reactivity tracking |
| Language | JSX (JS + HTML mixed) | TypeScript + HTML templates | Templates (HTML-based) or JSX |
| Data Binding | One-way (explicit setState) | Two-way ([(ngModel)]) | Two-way (v-model) with one-way as default |
| State Management | BYO (Redux, Zustand, Jotai) | RxJS + Services (built-in) | Pinia (official), Vuex (legacy) |
| Learning Curve | Low entry, high ceiling | Steep (DI, RxJS, decorators, modules) | Low entry, moderate ceiling |
| Bundle Size | ~45KB (core + DOM) | ~150KB+ (full framework) | ~35KB (core) |
| Ecosystem | Massive but fragmented | Cohesive but opinionated | Growing, more unified |
- React gives you a render function and says “figure out the rest.” This flexibility is both its strength (choose the best tool for each job) and weakness (decision fatigue, inconsistent projects).
- Angular provides everything: routing, HTTP, forms, DI, testing, i18n. This consistency helps large teams but the learning curve is steep.
- Vue sits in between: provides official solutions for common needs (Pinia, Vue Router) but makes them optional.
- If you were starting a new enterprise project with a team of 30 engineers, which would you choose and why?
- How does Angular’s change detection with Zone.js compare to React’s re-render + diff model?
- What is the “signals” movement (SolidJS, Angular Signals, Vue reactivity, potential React signals) and how might it change this landscape?
47. Closures in Hooks (Stale Closures)
47. Closures in Hooks (Stale Closures)
useEffect callback is created during the first render when count = 0. The [] dependency array means React never re-creates this callback. The count variable inside the closure is forever 0.Fix 1 — Add to dependency array:useEffect sets up a WebSocket message handler that accesses messages state. With [] deps, the handler always sees the initial empty array. New messages are lost because setMessages([...messages, newMsg]) spreads an empty array. Fix: setMessages(prev => [...prev, newMsg]).What interviewers are really testing: Deep understanding of JavaScript closures and how they interact with React’s render model. This separates mid-level from senior developers.Red flag answer: Not being able to explain why the closure is stale, or solving it by removing the dependency array entirely (causes the effect to run every render).Follow-up:- Write a
useLatest(value)hook that always returns a ref with the latest value. When is this useful? - How does
useEvent(proposed React RFC) solve the stale closure problem? What would its API look like? - Can stale closures occur with
useCallback? Give an example and the fix.
48. `dangerouslySetInnerHTML`
48. `dangerouslySetInnerHTML`
innerHTML property. Named deliberately to warn developers about the security risk of injecting unescaped HTML.{{ __html: ... }}) is intentional friction. You cannot accidentally use it — the explicitness forces you to acknowledge the risk.XSS (Cross-Site Scripting) Risk: If htmlContent comes from user input or an untrusted source, an attacker can inject:- DOMPurify: Sanitize HTML before injection. Removes all scripts, event handlers, and dangerous attributes:
- Allowlisting tags: Only permit specific HTML tags (e.g.,
<p>,<strong>,<em>,<a>). - Markdown rendering: Use a Markdown parser (like
react-markdown) that produces React elements, not raw HTML.
dangerouslySetInnerHTML without mentioning sanitization or XSS risks.Follow-up:- What specific XSS attack vectors does
dangerouslySetInnerHTMLexpose? Name at least three. - How does DOMPurify work under the hood? What does it strip out?
- What is a Content Security Policy (CSP) and how does it provide defense-in-depth against XSS even if sanitization fails?
49. PureComponent vs Component
49. PureComponent vs Component
PureComponent automatically implements shouldComponentUpdate with a shallow comparison of props and state. Component always re-renders when setState is called or the parent re-renders, unless you manually implement shouldComponentUpdate.prevValue === nextValue. This works for primitives but fails for objects/arrays that are recreated each render (same data, different reference).The Same Trap as React.memo: If the parent passes style={{ color: 'red' }} inline, the object reference is new every render, defeating PureComponent.Modern Equivalent: React.memo for function components is the modern replacement. Most new code uses function components + React.memo instead of PureComponent.What interviewers are really testing: Understanding of shallow comparison and when it helps vs when it is defeated by new references.Red flag answer: “PureComponent is always better than Component” — misses the overhead of shallow comparison and the new-reference trap.Follow-up:- What exactly does “shallow comparison” check? Walk through the algorithm.
- Can
PureComponentcause bugs? Give a scenario where it skips a necessary re-render. - Why is
React.memopreferred overPureComponentin modern React?
50. Flux Architecture pattern
50. Flux Architecture pattern
- Action: A plain object describing what happened (e.g.,
{ type: 'ADD_TODO', text: 'Buy milk' }). - Dispatcher: Central hub that receives ALL actions and broadcasts them to ALL registered stores. Only one dispatcher per app.
- Store: Holds state and logic. Receives actions from the dispatcher, updates internal state, emits a “change” event.
- View (React Components): Listen to store change events, re-render with new data.
- Replacing multiple stores with a single store.
- Replacing the dispatcher with a pure
reducefunction. - Making state immutable (enables time-travel debugging).
- Adding middleware for side effects.
useReducer follow the same unidirectional data flow principle. Understanding Flux helps you understand why modern state management works the way it does.What interviewers are really testing: Historical context and architectural thinking. Can you trace the evolution from MVC to Flux to Redux to modern state managers?Red flag answer: Not knowing that Flux is the predecessor to Redux, or confusing Flux with Redux.Follow-up:- What specific problems did bidirectional data flow cause at Facebook that motivated Flux?
- How did Redux improve upon Flux’s architecture? What did it simplify?
- Is the “unidirectional data flow” principle still relevant in a world of Server Components and server actions?
8. React 19 & Future (Bonus)
51. React Compiler (Forget)
51. React Compiler (Forget)
useMemo, useCallback, and React.memo-equivalent optimizations where beneficial.How It Works:- Analyzes the component’s data flow using Static Single Assignment (SSA) form.
- Identifies which values depend on which inputs.
- Wraps computations and function creations with memoization guards.
- Produces optimized JavaScript that is functionally equivalent to hand-written memoization but comprehensive and consistent.
- No more manual
useMemo,useCallback, orReact.memo. The compiler handles it. - No more “did I forget to memoize this callback?” performance bugs.
- The compiler memoizes more aggressively and correctly than humans typically do.
- Existing code with manual memoization still works — the compiler respects it.
'use no memo' directive to opt out specific components.Adoption: The React Compiler is available in React 19 and can be added via a Babel plugin. Instagram’s web app has been running it in production. Meta reported measurable performance improvements across their apps.What interviewers are really testing: Awareness of the React ecosystem’s direction and understanding of why automatic memoization is possible (pure function assumption).Red flag answer: “The compiler makes React faster” without understanding what it actually does (memoization) or what assumptions it requires (purity).Follow-up:- What happens if a component has impure behavior (mutates external state in render)? Can the compiler handle this?
- How does the compiler decide what is worth memoizing vs what is too cheap to bother?
- Does the React Compiler make learning
useMemo/useCallbackunnecessary? Should bootcamps still teach them?
52. `use` Hook
52. `use` Hook
use hook is a new React 19 primitive that reads the value of a resource (a Promise or a Context). Its most revolutionary property: it can be called conditionally — unlike all other hooks.use Can Be Conditional: Unlike useState/useEffect which store state on the Fiber’s linked list (position-dependent), use is a read-only operation. It does not store anything — it reads from a resource. No linked list slot is consumed, so call order does not matter.use with Promises: When use encounters an unresolved Promise, it throws the Promise (Suspense protocol). The nearest <Suspense> boundary catches it and shows the fallback. When the Promise resolves, React re-renders and use returns the value.use with Context: Functionally equivalent to useContext, but with conditional call capability. This simplifies patterns where you want to read context only in certain branches.What interviewers are really testing: Understanding of how use breaks the “rules of hooks” and why it can, and how it integrates with Suspense.Red flag answer: Not knowing that use can be conditional, or confusing it with a data-fetching hook (it reads resources, it does not initiate fetches).Follow-up:- How does
useinteract with Suspense? What happens when the Promise rejects? - Why can
usebe called conditionally whenuseStatecannot? Explain the internal difference. - How would you use
useto replace theuseEffect+ loading state pattern for data fetching?
53. Actions (Server Functions)
53. Actions (Server Functions)
onSubmit handler -> event.preventDefault() -> setState(loading: true) -> fetch('/api/...') -> setState(loading: false, data/error). Actions collapse this into a declarative API with automatic pending states.useActionState (formerly useFormState):<form action={...}> degrades to a standard HTML form submission. When JS loads, React enhances it with client-side handling and pending states. This is a major win for slow connections and accessibility.What interviewers are really testing: Understanding of the new data mutation model and how it simplifies form handling. Also: awareness of progressive enhancement.Red flag answer: Thinking server actions are just API endpoints — they are deeply integrated with React’s rendering, caching, and revalidation model.Follow-up:- How do server actions differ from a regular API endpoint? What serialization/deserialization does React handle?
- What is progressive enhancement in the context of server actions? Why does it matter?
- How do you handle validation errors in server actions? Where does the error state live?
54. `useOptimistic`
54. `useOptimistic`
useOptimistic shows the expected result to the user immediately while a server action is in progress, then reconciles with the actual response.useOptimistictakes the “real” state (from server/props) and an update function.- When you call
addOptimisticTodo, it immediately applies the update function to produce a “temporary” state. - The component re-renders with the optimistic data (the new todo appears instantly).
- When the action resolves and the real
todosprop updates, the optimistic state is replaced by the real state. - If the action fails, the optimistic state is automatically rolled back to the last known real state.
- What happens if the server action fails? How does the rollback work visually?
- Before
useOptimistic, how would you implement optimistic updates withuseStateanduseEffect? What is the complexity? - When should you NOT use optimistic updates? Give a scenario where showing premature confirmation is dangerous.
55. Document Metadata
55. Document Metadata
<title>, <meta>, and <link> tags inside any component, and React automatically hoists them to the document <head>.react-helmet (or react-helmet-async) — a popular third-party library that intercepted <title> and <meta> rendering and moved them to <head>. Now this behavior is built into React.How It Works: React recognizes these specific HTML tags and during the commit phase, inserts/updates them in the document <head> rather than where they appear in the component tree. This includes deduplication — if two components set <title>, the last one rendered wins.SSR Integration: Document metadata works with React’s streaming SSR. Metadata is included in the initial HTML response, which is critical for SEO (search engine crawlers need <title> and <meta> in the initial HTML, not injected by client-side JS).What interviewers are really testing: Awareness of the evolving React API and the SEO implications of metadata management.Red flag answer: Still recommending react-helmet without knowing about native support.Follow-up:- How does React handle duplicate/conflicting metadata (e.g., two components both set
<title>)? What is the resolution strategy? - Why was managing metadata in React traditionally hard? How did the component model conflict with the
<head>placement requirement? - How does this interact with SSR streaming? When does the metadata get sent in the HTML stream?
56. Asset Loading
56. Asset Loading
precedence: The precedence prop on <link rel="stylesheet"> tells React the importance order. React ensures higher-precedence stylesheets are loaded first, preventing FOUC. Without this, dynamically loaded CSS can flash unstyled content.What Changes:- Stylesheets declared in components are deduplicated (same
hrefloaded once). - React waits for critical stylesheets to load before revealing the Suspense boundary content.
- Fonts can preload so they are available before the component that uses them renders.
- Scripts are deduplicated and loaded in the correct order.
onLoad callbacks. Libraries like loadable-components handled some of this.What interviewers are really testing: Understanding of web performance concepts (FOUC, CLS, font loading) and how React 19 addresses them.Red flag answer: Not understanding the precedence prop or why stylesheet loading order matters.Follow-up:- What is Flash of Unstyled Content (FOUC) and how does React 19’s stylesheet support prevent it?
- How does this interact with Suspense? What happens if a stylesheet takes 5 seconds to load?
- Compare this approach to CSS-in-JS (styled-components, Emotion) in terms of performance and developer experience.
57. Web Components support
57. Web Components support
- React treated all Custom Element attributes as strings (HTML attributes), not properties. Complex values (objects, arrays) were serialized to
[object Object]. - Custom Events from Web Components were not handled by React’s synthetic event system.
refon Custom Elements worked, but property setting was inconsistent.
- React now distinguishes between attributes (string-serializable) and properties (any JS value). If a prop matches a property on the element, React sets it as a property.
- Event listeners for custom events work with
onEventNameprops. classNamevsclassis handled correctly for Custom Elements.
- What is the difference between HTML attributes and DOM properties? Why does this matter for Custom Elements?
- When would you choose Web Components over React components? What are the trade-offs?
- How do Shadow DOM encapsulation and React’s event system interact?
58. Ref as a Prop
58. Ref as a Prop
ref as a regular prop to function components, eliminating the need for forwardRef.Before (React 18 and earlier):forwardRef Existed: Function components are not instances (unlike class components). There is no “this” to attach a ref to. forwardRef was the mechanism to explicitly thread the ref through. But it added boilerplate, confused TypeScript generics, and was a common source of bugs.Impact on Component Libraries: Design systems with dozens of forwarded-ref components (every input, button, and container) can now remove forwardRef wrappers, simplifying code significantly.Cleanup Callbacks (new in React 19): Ref callbacks can now return a cleanup function:forwardRef existed and how React 19 simplifies the ref API.Red flag answer: Not knowing what forwardRef was or why it was needed.Follow-up:- Can you explain why class components did not need
forwardRefbut function components did? - How does the ref cleanup callback change how we handle DOM side effects?
- What TypeScript changes are needed when migrating from
forwardRefto the new ref-as-prop pattern?
59. `<Context>` instead of `<Context.Provider>`
59. `<Context>` instead of `<Context.Provider>`
<Context> directly as a provider, instead of <Context.Provider>.Before (React 18):Context.Provider was an unnecessary layer of abstraction. Every React developer had to learn that Context objects have a .Provider property and that you render the Provider, not the Context itself. This was confusing, especially for newcomers.Deprecation: <Context.Provider> still works but is deprecated. It will be removed in a future major version. Migration is straightforward — just drop the .Provider part.What interviewers are really testing: Awareness of React 19 API changes and the principle of API simplification.Red flag answer: Not knowing about this change (forgivable if not yet using React 19) or thinking Context’s behavior changed (it did not — only the JSX syntax).Follow-up:- Does this change affect how Context values are compared or how re-renders work? (No — purely syntactic.)
- What other React 19 API simplifications follow this same principle of reducing boilerplate?
- How would you approach migrating a large codebase to the new Context syntax? Can you codemods this?
60. Directives ('use client', 'use server')
60. Directives ('use client', 'use server')
'use client': Marks a file as a Client Component boundary. The component and everything it imports will be included in the client JavaScript bundle. It can use hooks, event handlers, browser APIs, and state.'use server': Marks a file (or individual functions) as Server Actions. These functions execute on the server and can be called from client components. The framework serializes the arguments, sends them to the server, executes the function, and returns the result.- By default (in Next.js App Router), all components are Server Components (no directive needed).
'use client'opts a component into the client bundle — you are crossing the server-to-client boundary.'use server'creates a server-callable function — you are exposing a server endpoint that the client can invoke like an RPC call.
children props though). This is because client code ships to the browser and cannot import server-only code.Security Consideration: 'use server' functions are public API endpoints. Any client can call them with arbitrary arguments. Always validate and authorize inside server functions — do not trust the input just because it comes from your own UI.What interviewers are really testing: Understanding of the server/client component model, the boundary rules, and the security implications.Red flag answer: Thinking 'use server' makes a component a Server Component (it does not — it creates server-callable functions). Also: not mentioning the security implications of server functions.Follow-up:- What happens if you try to import
useStatein a file without'use client'? What error do you get? - Explain the serialization boundary between Server and Client components. What types can cross the boundary?
- How do you handle authentication/authorization in
'use server'functions? Walk through a secure implementation.
9. Advanced Topics (New Questions)
61. React Hydration Deep Dive
61. React Hydration Deep Dive
- Server renders the component tree to an HTML string and sends it to the client.
- Browser renders the HTML immediately (fast FCP).
- React’s client-side code loads and calls
hydrateRoot(document.getElementById('root'), <App />). - React walks the existing DOM and the virtual DOM it would have produced, attaching event listeners and internal Fiber structures without recreating the DOM.
- After hydration, the app behaves as a normal React SPA.
- Time-dependent values:
new Date(),Date.now(),Math.random()produce different values on server vs client. - Browser-only APIs:
window.innerWidth,localStorage,navigator.userAgentdo not exist on the server. - Browser extensions: Extensions inject elements/attributes into the DOM.
- Conditional rendering based on auth: Server may not have the same auth state as the client.
suppressHydrationWarning prop silences warnings for known mismatches (e.g., server-rendered timestamps).Performance Impact: Hydration is not free. React must walk the entire DOM tree, which for large pages can take 100ms+. This creates the “uncanny valley” — the page looks ready but clicks do not work.React 18 Solutions:- Selective Hydration: React prioritizes hydrating components the user is interacting with (clicking/typing).
- Streaming SSR with Suspense: Server streams HTML progressively. Components inside
<Suspense>boundaries are hydrated independently.
- What is the “Time to Interactive” penalty of hydration? How can you measure it?
- Explain Selective Hydration. What happens if a user clicks a button that has not been hydrated yet?
- How do “Islands Architecture” frameworks (Astro) avoid the hydration cost that React pays? What is the trade-off?
62. Suspense Boundaries Strategy
62. Suspense Boundaries Strategy
<Suspense> determines what the user sees while content loads — and getting this wrong leads to bad UX.The Strategic Decisions:Too few boundaries (one at root): The entire app shows a spinner until everything loads. Slow but simple.- Wrap independent content sections: Navigation, main content, sidebar each get their own boundary. If the sidebar data is slow, the main content still shows.
- Group related content: A product card’s image, title, and price should load together (one boundary), not individually (three boundaries).
- Consider the skeleton: Each Suspense boundary needs a meaningful fallback. If you cannot design a good skeleton for a boundary, it is probably too granular.
- Nested boundaries: Suspense boundaries compose. An inner boundary’s fallback shows only if the outer boundary’s content has resolved.
- How do you decide where to place Suspense boundaries? What factors influence the decision?
- How does Suspense interact with Error Boundaries? Can a component both suspend and throw an error?
- What is the difference between Suspense for code splitting vs Suspense for data fetching?
63. State Management Architecture Decision
63. State Management Architecture Decision
-
Local UI State: Open/closed toggles, input values, hover states. Tool:
useState. Never goes global. If two components need the same toggle, lift state up, do not reach for a library. - Server/Remote State: Data from APIs — user profiles, product lists, search results. Tool: TanStack Query (React Query) or SWR. These handle caching, deduplication, background refresh, pagination, optimistic updates. Do not put server data in Redux — you end up reimplementing caching poorly.
- Global Client State: Theme, auth, feature flags, sidebar collapsed state. Tool: Context (for low-frequency) or Zustand/Jotai (for high-frequency). If state changes rarely, Context is fine. If it changes often and has many consumers, use a library with selectors.
-
URL State: Filters, sort, pagination, search query — anything the user should be able to bookmark or share. Tool: URL search params. Libraries:
nuqs, React Router’suseSearchParams. - Form State: Form values, validation, dirty tracking, submission state. Tool: React Hook Form or Formik. Do not build this yourself — form state is deceptively complex (touched, dirty, validation, array fields, dynamic fields).
- Your team has Redux managing server data, client state, and form state in one store. How would you incrementally migrate to a better architecture?
- Compare the mental model of atomic state (Jotai) vs store-based state (Zustand). When does each shine?
- How does TanStack Query’s stale-while-revalidate model change the way you think about “loading states”?
64. React Performance Profiling (Production)
64. React Performance Profiling (Production)
- Record a session, perform the interaction.
- Flamegraph view: Shows render duration per component, nested by tree structure. Look for wide bars (slow components) and unnecessary re-renders.
- Ranked view: Components sorted by render time. Focus on the top offenders.
- “Why did this render?”: Identifies the trigger — prop change, state change, parent render, or hook change.
- Record a performance trace during the slow interaction.
- Look for long tasks (blocks main thread for 50ms+).
- Identify if the bottleneck is React rendering (JavaScript), layout/reflow, or paint.
- Check for layout thrashing (forced synchronous reflows).
- React Profiler API: Wrap components in
<Profiler onRender={callback}>to measure render duration in production: - Core Web Vitals: LCP (Largest Contentful Paint), INP (Interaction to Next Paint), CLS (Cumulative Layout Shift). Tools: Google Lighthouse,
web-vitalslibrary, Vercel Analytics. - Custom performance marks:
performance.mark('dashboard-loaded')+performance.measure()for custom timing.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Slow initial render | Large bundle | Code splitting |
| Janky scrolling | Too many DOM nodes | Virtualization |
| Slow typing in input | Parent re-renders on each keystroke | Isolate input state, useDeferredValue |
| Slow filter/sort | Expensive computation on each render | useMemo |
| Slow list updates | All items re-render | React.memo + stable keys + stable callbacks |
- Walk me through profiling a specific slow interaction. What tools, in what order?
- How would you set up performance monitoring for a React app in production? What metrics would you track?
- What is INP (Interaction to Next Paint) and why is it replacing FID (First Input Delay) as a Core Web Vital?
65. Accessibility in React Applications
65. Accessibility in React Applications
aria- attributes — it is about building experiences that work for all users, including those using screen readers, keyboard navigation, voice control, and alternative input devices.Core Principles:-
Semantic HTML First: Use
<button>not<div onClick>. Use<nav>,<main>,<article>,<header>. Semantic elements provide free accessibility — built-in keyboard handling, screen reader announcements, and proper focus management. -
Keyboard Navigation: Every interactive element must be reachable and usable with keyboard alone. Tab order should be logical. Custom components need
onKeyDownhandlers for Enter/Space (buttons), arrow keys (menus/tabs), Escape (close dialogs). -
Focus Management: When a modal opens, move focus into it. When it closes, return focus to the trigger. When content updates (SPA navigation), announce it to screen readers.
useRef+element.focus()+aria-liveregions. -
ARIA When Necessary:
aria-livefor dynamic content: When React updates content (search results, notifications), screen readers do not automatically announce changes. Usearia-live="polite"for non-urgent updates andaria-live="assertive"for urgent ones.- Route change announcements: SPA navigation does not trigger a page reload, so screen readers do not announce the new page. Use a visually hidden live region that announces the new page title.
htmlForinstead offor: JSX useshtmlForon<label>elements.
eslint-plugin-jsx-a11y: Catches common issues at lint time (missingalton images, click handlers without keyboard support).@testing-library/react: Queries by role encourage accessible markup.- Axe DevTools: Browser extension for runtime a11y auditing.
- Lighthouse a11y audit: Automated scoring.
- How would you make a custom dropdown/select component fully accessible? Walk through the keyboard and screen reader requirements.
- What is the difference between
aria-label,aria-labelledby, andaria-describedby? When do you use each? - How do you announce SPA route changes to screen readers in a React Router application?
66. Micro-Frontend Architecture with React
66. Micro-Frontend Architecture with React
-
Build-Time Integration (Module Federation): Webpack Module Federation or Vite’s federation plugins allow separate builds to share modules at runtime. App Shell loads remote components from different bundles:
- Runtime Integration (iframes): Each micro-frontend runs in an iframe. Complete isolation but poor UX (no shared state, routing coordination is complex, performance overhead).
- Runtime Integration (Web Components): Each micro-frontend exposes a Custom Element. Framework-agnostic but loses React-specific benefits (Context, Suspense).
- Runtime Integration (Single-SPA): A meta-framework that mounts/unmounts different React (or non-React) apps based on URL routes. Each app is fully independent.
- Shared dependencies: If both micro-frontends bundle React, the user downloads it twice. Module Federation solves this with shared singleton modules.
- Consistent styling: Different teams may use different CSS approaches. Design tokens and a shared component library help.
- Routing coordination: URL changes need to be handled consistently across micro-frontends.
- Shared state: Auth tokens, user preferences, and cart state need to flow across boundaries. Options: shared event bus, URL state, or a minimal shared store.
- How does Module Federation handle shared React instances? What happens if two micro-frontends need different React versions?
- What is the impact of micro-frontends on Core Web Vitals? How do you optimize performance?
- Compare Module Federation vs Single-SPA vs iframes. When would you choose each?
67. `useSyncExternalStore` Hook
67. `useSyncExternalStore` Hook
useSyncExternalStore is a React 18 hook designed for subscribing to external data sources (third-party stores, browser APIs, legacy pub/sub systems) in a way that is safe under Concurrent Mode.Why It Exists: In Concurrent Mode, React can read an external store’s value at the start of rendering and then again at commit time. If the value changed between reads (because the store was updated externally), the UI would show inconsistent data — a “tearing” bug. useSyncExternalStore guarantees consistency.- Browser APIs as stores:
matchMedia,navigator.onLine,window.innerWidth— these are external data sources that change outside React’s control. - Third-party state libraries: Zustand, Jotai, and Redux all use
useSyncExternalStoreinternally. - Legacy code integration: Subscribing to an existing event emitter or MobX observable from a React component.
getServerSnapshot: The third argument provides a value during SSR (where browser APIs like matchMedia do not exist). This prevents hydration mismatches.useState + useEffect to subscribe to external stores (this can cause tearing in Concurrent Mode) or not knowing what “tearing” means.Follow-up:- What is “tearing” in the context of Concurrent Rendering? Give a concrete example of how it manifests.
- Before
useSyncExternalStore, how did libraries like Redux subscribe to stores? What bug did this fix? - How do Zustand and Jotai use
useSyncExternalStoreunder the hood?
68. React Design Patterns for Scale
68. React Design Patterns for Scale
/components, /hooks, /utils), group by feature:fetch directly in components. Create an API layer that handles auth tokens, base URLs, error transformation, and retry logic:/components” or not having opinions about code organization.Follow-up:- How would you enforce feature boundaries to prevent features from importing each other’s internals?
- Compare the container/presenter pattern with the custom hook pattern. When is each more appropriate?
- How do you handle cross-cutting concerns (auth, analytics, error tracking) that span multiple features?