React JS Architecture: React JS Scenario Based questions and answers

System Design, Enterprise State, Advanced Rendering, and Scalable Architectures. Zero code, pure architectural strategy.

Micro-Frontends, Enterprise Testing, Strangler Migrations, and Edge Computing.

1. Enterprise State Architecture

Q1
As an architect, how do you decide between the native Context API and a dedicated state manager like Redux or Zustand for a large-scale application?
The Core Architectural Concept: Context is a dependency injection tool, not a reactive state manager. Choosing between them relies on assessing the velocity of state changes and the granularity of UI updates required. The Why & How: Context lacks native selector support; any update to a Context provider forces a re-render of all its consumers, regardless of whether they need the specific changed data. If state is highly volatile, this causes massive CPU spikes. You mandate Context for low-velocity, globally read data (like user preferences or themes). You mandate dedicated state managers (which use external stores and granular subscriptions) for high-velocity, highly interacted data. Real-World Scenario: In a global e-commerce platform, the user’s selected language and dark-mode settings are managed via Context because they change rarely. However, the shopping cart and live inventory trackers are managed via Zustand to ensure that a rapid influx of inventory updates doesn’t forcefully re-render the entire navigation header on every tick.
Q2
How do you architect the separation of “Server State” from “Client State”, and what problems does this separation solve?
The Core Architectural Concept: Treating the backend database as the absolute source of truth and treating the frontend merely as an intelligent, synchronized cache. The Why & How: Historically, teams copied API responses into global Redux stores, blurring the line between local UI toggles and database records. This led to stale data and complex manual sync logic. As an architect, you mandate libraries like React Query or Apollo. Client state (modal open/closed, form input) remains in local components. Server state (user profiles, feed data) is handed off entirely to the caching layer, which automatically handles background polling, deduplication, and cache invalidation. Real-World Scenario: Designing a collaborative SaaS document editor. The list of active collaborators is Server State, managed by a caching library that polls the server silently. The UI toggle that opens the “Share” menu is purely Client State. Decoupling them ensures that when a new user joins the document, only the collaborator list updates, without accidentally resetting the state of the active dropdown menus.
Q3
How do you architect event-driven communication between isolated Micro-frontends built in React?
The Core Architectural Concept: Utilizing an Event Bus or the native browser CustomEvent API to decouple independent application silos. The Why & How: In a micro-frontend architecture, Team A’s React app and Team B’s React app might live on the same DOM but do not share a React tree or memory space. Prop drilling or Context sharing is impossible. To maintain loose coupling, you implement a globally accessible event bus. Micro-frontends emit standardized, typed events to the `window` object, and other micro-frontends subscribe to these events to trigger their own internal state updates. Real-World Scenario: A banking portal where the “Navigation” is one React app and the “Funds Transfer” is another. When a user successfully transfers money, the Transfer app emits a `TRANSACTION_SUCCESS` custom event. The Navigation app listens for this event and triggers a background refetch of the user’s account balance, updating the header. Neither app needs to know about the other’s internal codebase.
Q4
When migrating an enterprise monorepo, how do you evaluate Redux Toolkit vs. Zustand vs. Jotai?
The Core Architectural Concept: Matching the state management paradigm to the fundamental structural needs of the application’s domain logic. The Why & How: Redux Toolkit is the heaviest but provides the strictest guardrails; it is ideal for massive teams where predictable, unidirectional data flow and exhaustive audit trails (time-travel debugging) are non-negotiable. Zustand is ideal for leaner apps requiring global access without boilerplate. Jotai (atomic state) is required when the application is inherently structural or graph-like, where individual isolated nodes need independent state without centralizing it. Real-World Scenario: For a heavily regulated financial trading dashboard where every user action must be auditable, Redux is mandated. However, for a sister project building a free-form whiteboard application where users can spawn 10,000 independent sticky notes, the architect chooses Jotai. An atomic approach allows a single sticky note to be dragged and updated at 60fps without triggering the overhead of a centralized Redux store.
Q5
How do you architect a React application to handle ultra-high-frequency real-time data streams without freezing the main thread?
The Core Architectural Concept: Decoupling the data ingest rate from the React render cycle using memory buffers and browser repainting intervals. The Why & How: React is not designed to process thousands of state updates per second; attempting to do so will freeze the browser. The architectural solution is to capture the WebSocket stream in a plain JavaScript memory variable (a buffer) outside of React’s lifecycle. You then use a throttle function or the browser’s native animation frame API to sample that buffer at a safe interval (e.g., 10 times a second) and flush only the latest snapshot into React state. Real-World Scenario: Building a live cryptocurrency order book. The WebSocket fires 500 price updates per second. If we pipe this directly to React state, the DOM crashes. By buffering the data in a mutable reference and using an animation frame loop to read the buffer and set state every 100ms, the UI remains perfectly responsive and visually accurate, completely ignoring the interstitial noise.

2. Advanced Rendering & Performance Strategy

Q6
How do you design an architecture to completely mitigate hydration mismatch errors in globally distributed Server-Side Rendered (SSR) applications?
The Core Architectural Concept: Enforcing strict purity in the initial render pass and delaying environment-specific data injections until the client assumes control. The Why & How: Hydration errors occur when the server’s HTML string differs from the client’s first Virtual DOM calculation. This is almost always caused by using browser-only APIs or locale-specific data (like timezones or random numbers) during the render body. As an architect, you mandate that all components must render a generic fallback or standard UTC value on the first pass. Environment-specific overrides are only permitted inside effect hooks, which guarantee they execute post-hydration. Real-World Scenario: An international travel booking site displays the message “Good Morning” or “Good Evening” based on the user’s local time. The server in Virginia cannot know the user’s local time in Tokyo. The architect mandates that the server always renders a generic “Welcome”. Once the application hydrates on the user’s browser in Tokyo, an effect hook reads the local system clock and swaps the text to “Good Morning”, completely avoiding a hydration crash.
Q7
What is your architectural approach to preventing memory leaks in a massive, long-lived Single Page Application (SPA)?
The Core Architectural Concept: Strict lifecycle containment and adopting a standard of defensive cleanup across all external subscriptions. The Why & How: In SPAs, the browser never hard-refreshes. If a component establishes a connection to the outside world (intervals, event listeners, WebSockets, intersection observers) and is later unmounted by the router, that connection remains in memory, holding onto DOM references and bloating the heap. The architectural standard must enforce that every side-effect that creates a persistent subscription must simultaneously return a cleanup function to tear it down. Furthermore, abort controllers must be standard protocol for all network requests. Real-World Scenario: A dashboard features an infinite-scrolling feed containing hundreds of embedded video players. Without strict memory management, scrolling past a video leaves its intersection observer and media decoders active in the background. After 10 minutes of scrolling, the mobile device runs out of RAM and the app forcefully crashes. Architecturally enforcing cleanup routines ensures the video players are completely garbage-collected the moment they leave the DOM.
Q8
How do React Server Components (RSCs) fundamentally change your approach to bundle size and API layer architecture?
The Core Architectural Concept: Shifting non-interactive component logic entirely to the server, resulting in zero-kilobyte client payloads and the elimination of intermediary API endpoints. The Why & How: Historically, we built backend APIs just to serve data to React, and the client downloaded heavy libraries (like Markdown parsers or date formatters) just to render that data. With RSCs, the architecture flattens. Because Server Components run exclusively on the server and are stripped from the JS bundle, an architect can securely query the database directly from the component body and utilize massive backend libraries. The client only downloads the final HTML string and the tiny interactive islands. Real-World Scenario: A documentation website requires a heavy 5MB library to parse complex markdown with syntax highlighting. In a traditional SPA, users must download that 5MB parser. By migrating the `ArticleBody` to an RSC, the server handles the parsing. The user downloads 0MB of parsing logic, and you completely eliminate the need to build and maintain a `/api/get-parsed-article` backend route.
Q9
Compare “Islands Architecture” (e.g., Astro) with the “Next.js App Router” paradigm. When would you choose one over the other?
The Core Architectural Concept: Choosing between isolated pockets of interactivity vs a deeply integrated, globally interactive component tree. The Why & How: Islands architecture defaults to shipping zero JavaScript. It renders static HTML and allows you to surgically inject small, isolated React applications (“islands”) only where needed. Next.js App Router uses Server Components, which also reduce JS, but maintains a holistic React tree, allowing complex client-side routing and state preservation across page transitions. Real-World Scenario: For a massive publishing company (like the New York Times) where 95% of the page is static text and the only interactivity is a newsletter signup form, the architect chooses Islands Architecture. The overhead of a full React router is wasted. However, for a complex B2B SaaS dashboard where sidebars, modals, and tables all need to share global state and transition seamlessly without full page reloads, the Next.js App Router is the vastly superior choice.
Q10
How do you design a rendering strategy for an application that requires extreme SEO but also contains highly personalized, secure user data?
The Core Architectural Concept: Implementing a hybrid rendering architecture utilizing Static Site Generation (SSG) for the public shell and Client-Side Fetching for the secure payloads. The Why & How: Server-Side Rendering secure user data on the initial request disables CDN caching, making the site slow and exposing it to security risks if caches are misconfigured. Instead, the architect designs a public “skeleton” of the page that is statically generated and cached globally at the edge. Once the crawler parses this fast, public HTML, the real user’s browser kicks in, verifies their secure session, and fetches the personalized data purely on the client side to populate the skeleton. Real-World Scenario: A modern public profile on a social network. The user’s bio, public posts, and profile picture are statically generated for Google crawlers. However, the “Edit Profile” buttons, private messages, and the “Follows You” indicators are never rendered by the server. They are fetched client-side so that sensitive relational data is never accidentally captured by a global CDN node.

3. Design Systems & Component Architecture

Q11
How do you architect a “Headless UI” component library, and why is this pattern critical for enterprise scaling?
The Core Architectural Concept: Completely decoupling behavioral logic and accessibility state from visual markup and CSS. The Why & How: In massive enterprises, multiple products often share the same underlying logic but have radically different brand guidelines and CSS frameworks. If you hardcode CSS into your component library, it becomes inflexible. Headless architecture provides complex logic (keyboard navigation, ARIA attributes, focus management) via custom hooks or renderless components. The consuming team provides their own DOM elements and CSS, applying the headless logic to them. Real-World Scenario: A conglomerate owns a luxury brand and a budget brand. Both need a complex combobox dropdown. Instead of building one massive component with hundreds of style props, the core engineering team builds a headless `useCombobox` hook. The luxury team uses the hook to build a sleek, minimalist dropdown, while the budget team uses the exact same hook to build a chunky, colorful dropdown. They share 100% of the complex logic and 0% of the CSS.
Q12
What is your strategy for architecting highly dynamic forms with hundreds of fields and complex conditional validation rules?
The Core Architectural Concept: Shifting from declarative hardcoded markup to JSON-driven schema generation combined with uncontrolled component optimization. The Why & How: Hardcoding a 500-field form is unmaintainable. Tying 500 inputs to a single React state object causes catastrophic re-render lag on every keystroke. The architect mandates a schema-driven approach (using tools like JSON Schema or Zod) where the backend dictates the form structure. On the frontend, libraries like React Hook Form are utilized to manage state via uncontrolled inputs, ensuring that typing in Field 499 does not cause the other 499 fields to re-render. Real-World Scenario: An insurance claim application portal. The questions change entirely based on whether the user selects “Auto Incident” or “Home Flood.” The backend sends a JSON schema dictating the required fields for the specific incident. A recursive React engine parses this schema to generate the UI dynamically. Validation rules are isolated to the specific input nodes, ensuring a fluid 60fps typing experience despite the massive complexity of the form.
Q13
How do you architect the deployment and versioning of an internal Design System used by 50 different React projects?
The Core Architectural Concept: Treating the Design System as an independent product utilizing semantic versioning, separate repositories, and strictly enforced deprecation cycles. The Why & How: If a design system is tightly coupled to a main app, other apps cannot use it. If it updates rapidly without versioning, it breaks production for consuming teams. The architecture requires packaging the components as an NPM module. Breaking changes (like renaming a prop) mandate a major version bump. To prevent ecosystem fragmentation, the design system team must provide automated “codemod” scripts that scan consumer codebases and automatically rewrite old component syntax to the new syntax. Real-World Scenario: The central design team updates the primary Button component, changing the `variant=”outline”` prop to `appearance=”ghost”`. They release this as version 3.0. Consumer teams remain safely on version 2.0. When a team is ready to upgrade, they run a provided script in their terminal that safely finds and replaces all instances of the old prop across their entire codebase, minimizing integration friction.
Q14
When would you advocate for the Compound Components pattern over a standard Configuration Object pattern for complex widgets?
The Core Architectural Concept: Prioritizing declarative layout flexibility and inversion of control over monolithic, prop-heavy configuration. The Why & How: A Configuration pattern forces developers to pass massive, unreadable JSON objects into a single component to define its layout. This scales poorly when minor visual tweaks are needed. Compound Components rely on implicit state sharing (via Context) between a parent and its children. This allows the consumer to write clean, standard JSX, easily rearranging child elements or injecting custom markup without touching the core logic. Real-World Scenario: Architecting a `` component. If using a config object, injecting a custom sparkline chart into a specific cell requires messy callback functions embedded in JSON. By using compound components (``, ``, ``, ``), the consumer can simply drop their custom `` component directly inside the specific `` tags in their JSX, resulting in vastly superior developer ergonomics.
Q15
How do you enforce and monitor strict Web Accessibility (a11y) standards across a massive React codebase?
The Core Architectural Concept: Implementing a multi-layered defense system integrating static analysis, automated DOM testing, and strict CI/CD gatekeeping. The Why & How: Accessibility cannot be an afterthought. The architect enforces a strict pipeline: First, IDE linters are configured to instantly flag missing ARIA attributes or alt tags during development. Second, unit tests must query the DOM strictly by accessible roles (e.g., finding buttons by their text or ARIA labels) rather than generic CSS classes. Finally, the CI/CD pipeline runs automated accessibility audits against the built application. If the audit score drops below a mandated threshold, the deployment is hard-blocked. Real-World Scenario: A junior developer builds a custom toggle switch using a colored `div` with an `onClick` handler. The IDE immediately warns them that interactive elements require keyboard navigation. They ignore it and push to staging. The CI/CD pipeline runs the automated audit, detects a critical focus-management violation on the new route, and blocks the merge into the main branch, preventing the compliance violation from ever reaching production.

4. Build, Bundling, and Delivery

Q16
As an architect, how do you evaluate migrating a legacy enterprise React application from Webpack to Vite or Turbopack?
The Core Architectural Concept: Weighing the massive developer experience (DX) and build speed improvements against the risk of abandoning mature, highly customized build ecosystems. The Why & How: Webpack is slow due to its bundle-everything-first architecture, but it possesses a decade of battle-tested plugins for edge cases. Modern bundlers utilize native ES modules and languages like Rust or Go to deliver instantaneous hot-module replacement and drastically faster CI/CD builds. The architectural decision hinges on auditing the legacy Webpack config. If the app relies heavily on obscure Webpack loaders or complex Module Federation, migration is extremely risky. If the config is relatively standard, the productivity gains of sub-second rebuilds justify the migration effort. Real-World Scenario: An engineering team of 50 developers waits 3 minutes for the local server to start and 10 seconds for a code change to reflect in the browser. By investing two weeks to migrate the build system to Vite, the architect reduces start times to 2 seconds and hot-reloads to 50ms. Across 50 developers, this eliminates hundreds of hours of idle waiting time per month, massive justifying the migration ROI.
Q17
How do you design an aggressive Code Splitting and Route Prefetching strategy to optimize Time to Interactive (TTI)?
The Core Architectural Concept: Slicing the application bundle into logical chunks and utilizing predictive network fetching based on user intent. The Why & How: Shipping a 10MB JavaScript file guarantees terrible performance. The architecture must mandate route-level code splitting so users only download the code for the page they are viewing. To prevent lag when they navigate to a new route, the architect implements predictive prefetching. By utilizing Intersection Observers or specific hover events on navigation links, the browser is instructed to quietly download the JavaScript chunk for the destination route in the background before the user even clicks. Real-World Scenario: A user is reading an article on a media platform. At the bottom is a link to the “Comments and Community” section, which requires a massive 2MB rich-text editor library. The architect’s system detects when the user scrolls near the link. It proactively fetches the 2MB chunk in the background. When the user finally clicks the link, the transition is instantaneous because the heavy payload is already sitting in the browser’s cache.
Q18
What is the most resilient way to manage environment variables and runtime configurations in a containerized React application?
The Core Architectural Concept: Decoupling build-time static variables from runtime dynamic configurations to allow a single Docker image to be promoted across multiple environments. The Why & How: Embedding environment variables during the build step creates a fatal flaw: you must compile a completely different artifact for Staging, UAT, and Production, violating standard CI/CD principles. The architect designs a solution where the React app fetches a static `config.json` file on load, or the hosting server dynamically injects the variables into the `window` object of the `index.html` file right as the document is served. Real-World Scenario: An application needs to connect to the Staging API and the Production API. Instead of running `npm run build` twice, the CI pipeline builds a single generic Docker image. When deployed to the Staging cluster, the cluster infrastructure mounts the staging URLs into the container’s environment, which the Node server injects into the HTML at runtime. This guarantees that the exact same tested binary is promoted to Production.
Q19
How do you approach polyfilling and legacy browser support in a modern React architecture without penalizing users on modern devices?
The Core Architectural Concept: Utilizing differential serving and targeted capability detection rather than shipping bloated baseline polyfills to all users. The Why & How: Forcing Chrome users to download polyfills for older browsers is an anti-pattern. The modern approach involves configuring the build system to generate two separate bundles: a lightweight modern bundle utilizing the latest syntax, and a heavier legacy bundle packed with polyfills. The server or the HTML document uses the `nomodule` script attribute to detect the browser’s capabilities and serves the appropriate payload. Real-World Scenario: An enterprise app must legally support a specific older browser version used by a government client. The architect sets up differential serving. When the government client visits the site, their browser executes the legacy bundle containing massive polyfills for array methods and promises. When a user on the latest mobile device visits, their browser ignores the legacy bundle entirely, downloading the sleek, highly optimized modern bundle.
Q20
How do you architect a Component-Driven Development workflow that guarantees UI stability across releases?
The Core Architectural Concept: Isolating UI development from application state and enforcing visual regression testing as a strict deployment gate. The Why & How: Building components directly inside a complex application often couples them tightly to global state and makes them impossible to reuse. The architect mandates the use of isolation tools like Storybook. Developers build the “dumb” components in isolation, defining all possible states (loading, error, empty). To guarantee stability, the CI pipeline integrates tools like Chromatic to take pixel-perfect screenshots of every component state during a pull request, flagging any unintended visual shifts for manual approval before merge. Real-World Scenario: A developer tweaks the global CSS to adjust the margin on a specific layout, unknowingly breaking the alignment of the core Application Header. Because the architect instituted visual regression testing, the CI pipeline takes a screenshot of the Header in Storybook, compares it to the master baseline, highlights the 5-pixel shift in red, and blocks the deployment until the CSS conflict is resolved.

5. Resiliency, Security, and Observability

Q21
How do you design an Error Boundary architecture that maximizes application uptime and aids rapid debugging?
The Core Architectural Concept: Implementing granular blast-radius containment alongside aggressive, contextual telemetry logging. The Why & How: A single unhandled exception in React unmounts the entire component tree, resulting in a white screen of death. An architect designs a tiered boundary system. A global boundary catches catastrophic routing failures. Feature-level boundaries wrap distinct widgets. More importantly, when a boundary catches an error, it doesn’t just show a fallback UI; it must silently serialize the exact component stack trace, the current routing state, and the user’s session ID, and transmit this payload to an observability platform. Real-World Scenario: An analytics widget on a complex dashboard receives a malformed payload and crashes. Because it is wrapped in a localized boundary, the widget turns into a gray box reading “Data Unavailable,” but the user can continue using the rest of the dashboard. Simultaneously, the boundary pushes the stack trace to Sentry, alerting the engineering team to the exact line of code that failed before the user even has a chance to submit a bug report.
Q22
What is your architectural strategy for preventing Cross-Site Scripting (XSS) in a React application that heavily features user-generated rich text?
The Core Architectural Concept: Defense-in-depth, relying on React’s native string escaping while enforcing strict, server-side sanitization policies for raw HTML injection. The Why & How: React inherently protects against basic XSS by treating all string variables as text, not HTML. However, rich text editors often require rendering actual HTML strings, forcing the use of the dangerous bypass API. The architectural mandate is twofold: First, raw HTML must never be rendered without first passing through a robust client-side sanitizer (like DOMPurify) configured to strip malicious scripts. Second, the backend API must be the ultimate arbiter, aggressively sanitizing payloads before they are ever stored in the database. Real-World Scenario: A forum application allows users to submit bold and italic text. A malicious user intercepts the API call and submits a payload containing a script tag designed to steal session cookies. Because the backend sanitizes the input, the script tag is stripped before it hits the database. Even if the backend failed, the frontend architect mandates that all rich text passes through DOMPurify before rendering, guaranteeing the malicious payload is neutralized before the browser can execute it.
Q23
How do you integrate heavy observability tools (like Datadog or FullStory) without causing significant degradation to the React render cycle?
The Core Architectural Concept: Offloading telemetry processing to Web Workers and deferring initialization until the main thread has completed critical rendering tasks. The Why & How: Session replay tools deeply instrument the DOM, attaching listeners to every scroll, click, and input. Initializing these tools synchronously blocks the main thread, destroying the application’s Time to Interactive score. The architect mandates that tracking scripts are loaded asynchronously and initialized only after the application registers an idle state. Furthermore, high-frequency telemetry events must be batched and processed in a Web Worker, keeping the main thread dedicated entirely to React’s rendering engine. Real-World Scenario: A streaming platform wants to record user sessions to debug complex UI interactions. Booting the tracking script during load delays the video player from rendering by 2 seconds. By deferring the script execution until after the video player has fully mounted and leveraging a background worker to compress the telemetry data, the platform achieves total observability with absolutely zero impact on the user’s perceived loading speed.
Q24
How do you architect seamless JWT authentication and token refresh cycles across multiple browser tabs without forcing the user to log in repeatedly?
The Core Architectural Concept: Utilizing HTTP-only cookies for storage, silent background refresh routines, and Broadcast Channels for cross-tab synchronization. The Why & How: Storing JWTs in local storage exposes them to XSS attacks. The architecture requires short-lived access tokens stored in memory and long-lived refresh tokens stored in secure, HTTP-only cookies. Before an access token expires, an Axios interceptor silently requests a new one. If the user has three tabs open, you use the browser’s Broadcast Channel API. When Tab A successfully refreshes the token, it broadcasts a message to Tabs B and C, allowing them to update their in-memory tokens without triggering redundant, conflicting network requests. Real-World Scenario: A user is writing a lengthy email in one tab and browsing files in another. The 15-minute access token expires. The file browser tab intercepts the next API call, pauses it, uses the secure cookie to fetch a new token, resumes the call, and broadcasts the new token to the email tab. The user hits “Send” on the email tab, and the request succeeds perfectly, entirely unaware that a complex security refresh occurred in the background.
Q25
What is your strategy for Graceful Degradation when a critical third-party API service experiences an outage?
The Core Architectural Concept: Implementing Circuit Breaker patterns, stale cache fallbacks, and feature toggles to protect the core application experience. The Why & How: Applications cannot crash simply because a minor microservice is down. The frontend architecture must anticipate failure. If an API request times out repeatedly, the API client should “trip a circuit breaker,” immediately returning localized fallback data or null rather than making users wait for subsequent timeouts. Furthermore, the UI must be designed to safely omit the broken widget entirely, or serve slightly stale data from the local cache, rather than throwing an unhandled exception. Real-World Scenario: An e-commerce product page relies on a third-party recommendation engine to show “Similar Items.” The recommendation engine experiences a catastrophic outage. Instead of the entire product page crashing or freezing, the frontend circuit breaker trips after the first failure. It tells the UI to simply hide the “Similar Items” section. The user can still read the product description, add the item to their cart, and checkout successfully, completely insulated from the backend disaster.

6. Micro-Frontends & Monorepo Scaling

Q26
As an architect scaling an engineering organization to 500+ frontend developers, how do you evaluate Webpack Module Federation versus Build-Time Composition (NPM packages)?
The Core Architectural Concept: Evaluating deployment autonomy versus strict versioning and application stability. The Why & How: Build-time composition requires publishing shared components as NPM packages. It guarantees stability because the host application locks versions, but it requires coordinating builds across teams; if the header team updates a package, the host team must rebuild and deploy to see it. Webpack Module Federation allows independent deployment at runtime. The header team deploys their chunk to a CDN, and the host application instantly consumes the new version without rebuilding. However, this introduces high runtime risk if the federated module introduces a breaking API change. Real-World Scenario: In an enterprise streaming platform, the Video Player team and the User Profile team work autonomously. Using Module Federation, the Video team can deploy a critical bug fix to the video player instantly, bypassing the massive 40-minute build pipeline of the core application. The architect mandates strict semantic versioning and contract testing to ensure the runtime injection doesn’t crash the host container.
Q27
How do you resolve shared dependency bloat (e.g., React being downloaded 5 times) in a decentralized Micro-Frontend architecture?
The Core Architectural Concept: Utilizing dependency sharing configuration to establish singletons at the host level. The Why & How: If five micro-frontends (MFEs) independently bundle React, the browser will download React five times, destroying performance and causing fatal React hook errors due to multiple instances. The architect must configure the Module Federation plugin to define React, React-DOM, and core design systems as “singleton shared dependencies.” The host container loads React once. When an MFE initializes, it checks the host’s memory; if React is present, it uses the host’s version instead of downloading its own. Real-World Scenario: A dashboard loads a “Chat Widget” MFE and a “Notification” MFE. Both are configured to share `react` and `styled-components`. The host provides these libraries. When the widgets boot up, they hook into the host’s single instance of React, reducing the total payload size by hundreds of kilobytes and ensuring context providers work seamlessly across MFE boundaries.
Q28
When would you advocate for a Monorepo (Nx/Turborepo) over a Polyrepo architecture for a React ecosystem?
The Core Architectural Concept: Centralizing governance, simplifying cross-project refactoring, and leveraging remote build caching. The Why & How: A Polyrepo approach (one repo per app/library) creates extreme friction when updating shared dependencies. A change to a core button component requires opening PRs in 15 different repositories. A Monorepo places all apps and packages in one repository. The architect pairs this with a smart build system (Turborepo) that understands the dependency graph. It only runs tests and builds for the specific applications affected by a code change, and it caches those builds globally. Real-World Scenario: A financial institution has a consumer web app, an admin portal, and a shared UI library. In a monorepo, a developer updates a critical accessibility flaw in the UI library. In a single pull request, they can run the tests for the consumer app and the admin portal to guarantee the change didn’t break them. The architect eliminates the “dependency hell” of syncing package versions across isolated repos.
Q29
How do you architect a unified, global routing strategy across independently deployed Micro-Frontends?
The Core Architectural Concept: Implementing an App Shell that acts as the master routing orchestrator, treating MFEs as dynamic route-level components. The Why & How: If MFEs try to manage the global browser history, they will collide and overwrite each other. The architecture dictates a “Host” or “App Shell” that owns the primary React Router. The Shell listens to the URL and dynamically imports the specific MFE bound to that route prefix. The MFEs are only permitted to manage their own internal sub-routes (Memory Router or scoped paths) and must emit events to the Shell if they need to trigger a global navigation event. Real-World Scenario: The Host application maps the `/checkout/*` route to the Payment MFE. When the user navigates to `/checkout/shipping`, the Host delegates rendering to the Payment MFE. If the Payment MFE needs to redirect the user back to the `/home` page after a successful purchase, it cannot mutate the history directly. It fires a `Maps_HOME` custom event, and the Host executes the route change, maintaining absolute structural control.
Q30
In a Micro-Frontend environment, how do you handle global authentication state without forcing every MFE to independently ping the identity provider?
The Core Architectural Concept: Centralizing authentication in the App Shell and passing down identity context via memory or custom events. The Why & How: Forcing 10 different MFEs to implement OAuth flows is a massive security and performance risk. The App Shell serves as the secure gatekeeper. It boots up, checks the secure HTTP cookie, negotiates with the Identity Provider, and establishes the user’s session. It then injects a sanitized `UserContext` object into the MFEs as a prop or exposes it via a globally synchronous API on the window object. Real-World Scenario: When the enterprise portal loads, the Shell verifies the JWT and retrieves the user’s roles. The Shell then lazy-loads the “Admin Panel” MFE and passes `userRoles={[‘ADMIN’]}` as a prop. The Admin Panel MFE inherently trusts the App Shell’s validation and uses the prop to render the appropriate views, completely decoupled from the actual cryptography and network requests required to validate the session.

7. Server-Side Rendering (SSR) & Edge Architecture

Q31
Why would an architect explicitly choose Client-Side Rendering (CSR) over SSR for a highly complex B2B SaaS dashboard?
The Core Architectural Concept: Evaluating Node.js compute overhead against SEO requirements and Time-to-Interactive (TTI) prioritization. The Why & How: SSR is required when SEO is critical or when users have slow devices. However, SSR requires the server to execute the entire React tree in Node.js for every request, which is incredibly CPU intensive. A B2B SaaS dashboard sits behind a login wall (zero SEO requirement) and features massive data grids. Using SSR would overwhelm the backend servers and delay the TTFB (Time to First Byte). An architect chooses CSR here to offload the rendering CPU cost entirely to the user’s powerful laptop, keeping infrastructure costs low and backend APIs highly responsive. Real-World Scenario: A cloud infrastructure monitoring tool features a dashboard with 50 live charts. Rendering 50 charts in Node.js on every refresh would cause severe server latency. The architect deploys the React app as a static bundle on a CDN (CSR). The browser downloads the shell instantly and establishes direct WebSockets to the data layer. The server is completely freed from UI rendering duties.
Q32
How do you plan a migration from the Next.js Pages Router to the App Router (React Server Components) for a massive production application?
The Core Architectural Concept: The Strangler Fig pattern utilizing Next.js’s native incremental adoption capabilities. The Why & How: A “big bang” rewrite of a 500-page app will freeze feature development for a year and introduce fatal bugs. The architect leverages the fact that Next.js allows the `pages/` and `app/` directories to coexist. The strategy dictates moving “leaf nodes” (isolated pages like About Us or static blog posts) to the App Router first. This builds team familiarity with Server Components. Highly complex, interactive pages remain in the Pages router. The migration happens route-by-route over 12 months, ensuring continuous delivery of business value. Real-World Scenario: An e-commerce platform migrates the `/faq` and `/contact` pages to the App Router on week one, reaping immediate bundle-size benefits. The massive `/checkout` flow remains in the Pages router. The Vercel infrastructure seamlessly routes traffic between the two architectures automatically. The checkout flow is only migrated in Q4, after the team has established strict internal design patterns for Server Actions and Suspense boundaries.
Q33
What is the architectural distinction between the Node.js Runtime and the Edge Runtime in modern React frameworks, and when do you use which?
The Core Architectural Concept: Balancing geographic latency and cold-start times against access to standard Node.js APIs and backend infrastructure. The Why & How: The Node runtime spins up a full server (often in a single region like US-East). It has full access to the file system, massive NPM libraries, and heavy database drivers, but suffers from slow “cold starts.” The Edge runtime uses lightweight V8 isolates deployed globally across hundreds of CDN nodes. It boots in milliseconds and executes code geographically close to the user, but cannot use native Node APIs (like `fs`) or traditional database ORMs. Real-World Scenario: The architect mandates the Edge Runtime for authentication middleware and A/B testing redirects. When a user in Tokyo requests a page, the Edge node in Tokyo instantly verifies their JWT and redirects them to the Japanese locale without pinging the Virginia server. However, the actual database query to fetch their dense financial history is routed to a Node.js Serverless function sitting in Virginia, directly adjacent to the PostgreSQL cluster, to prevent connection pooling exhaustion.
Q34
How do you architect a global cache invalidation strategy using Next.js Stale-While-Revalidate (SWR) and On-Demand Revalidation?
The Core Architectural Concept: Decoupling content delivery speed from content freshness via event-driven webhook invalidation. The Why & How: Relying purely on time-based revalidation (e.g., refresh every 60 seconds) means data is either unnecessarily rebuilt (burning CPU) or users see stale data for up to a minute. The architect designs an event-driven system. Pages are statically generated and cached at the Edge indefinitely (infinite TTL). When an editor updates a post in the headless CMS, the CMS fires a webhook to a secure Next.js API route. This route executes an On-Demand Revalidation command, instantly purging the specific URL from the global CDN cache. Real-World Scenario: A news organization publishes an article. It is cached globally at the edge. Millions of readers load the page in 50ms without touching the database. A journalist fixes a typo and hits “Update” in Contentful. Contentful pings the Next.js API, which surgically invalidates `/news/article-123`. The very next reader triggers a background rebuild, receives the fixed article, and the CDN caches the new version globally. Zero wasted rebuilds, instant TTFB.
Q35
What are the architectural risks of excessive Server-Side Rendering, and how do you implement Circuit Breakers to prevent cascading failures?
The Core Architectural Concept: Protecting the UI rendering layer from slow or failing backend microservices to ensure graceful degradation. The Why & How: If a React component blocks its server render while waiting for a slow inventory API, the user sees a blank screen until the API times out. If traffic is high, these hanging requests will exhaust the Node.js server’s connection pool, taking down the entire frontend. The architect must implement timeouts and circuit breakers within the SSR data fetching layer. If the inventory API takes longer than 2 seconds, the circuit breaker trips, returning `null` to the React component. Real-World Scenario: On a Black Friday sale, the review service microservice crashes under load. Because the architect wrapped the `fetchReviews` SSR call in a circuit breaker with a strict 1-second timeout, the Node server stops waiting for the dead service. The React page renders the product and checkout buttons perfectly, simply omitting the review stars. The company continues to make millions in sales despite a massive backend outage.

8. Legacy Migration & The Strangler Pattern

Q36
How do you architect a migration from a massive, 5-year-old AngularJS monolith to modern React without halting product development?
The Core Architectural Concept: The Strangler Fig pattern, utilizing micro-frontends or component-level wrappers to run two frameworks side-by-side. The Why & How: A complete rewrite is a business failure; it blocks new features for years. The architect sets up a dual-boot architecture. You embed a lightweight React rendering engine inside the legacy Angular app using a bridging tool (like `single-spa` or custom web components). All new features are built purely in React and injected into the Angular routing shell. Then, team by team, legacy Angular widgets are rewritten in React and swapped out. Over time, the React payload “strangles” the Angular payload until Angular can be safely deleted. Real-World Scenario: A healthcare portal needs a new “Telehealth Video” feature. The architect forbids building it in Angular. It is built as a pristine React application. An Angular wrapper component acts as a proxy, passing user session data down into the React application’s props. The business gets their new feature immediately, while the engineering team successfully establishes the beachhead for the React migration.
Q37
What is your strategy for migrating away from a monolithic, tightly coupled Redux store toward domain-driven local state and Server-State caching?
The Core Architectural Concept: Incremental state strangulation and shifting to a “Server as Source of Truth” paradigm. The Why & How: Tearing out Redux in one PR is impossible. The architect attacks the store by domain. First, they identify pure API caching reducers (e.g., `state.users.list`). They implement React Query alongside Redux. They swap out the `useSelector` hooks in the UI for `useQuery` hooks. Once the data flows through React Query, the legacy Redux actions, thunks, and reducers for that domain are deleted. This process is repeated until the Redux store only contains true global client state (like UI themes), at which point it can be replaced by Context or Zustand. Real-World Scenario: An application has a massive `ordersReducer` that spans 3,000 lines of code just to handle fetching, loading, and error states for user purchases. The architect implements React Query strictly for the `/api/orders` endpoint. Over a two-week sprint, developers replace Redux dispatches with the query hook. The 3,000 lines of boilerplate are deleted, massive performance gains are realized, and the rest of the application remains untouched and functional.
Q38
How do you architect a CSS migration (e.g., from Sass/Styled-Components to Tailwind CSS) across hundreds of legacy components?
The Core Architectural Concept: Strict encapsulation, codemod automation, and preventing CSS specificity collisions during the hybrid phase. The Why & How: Moving from a runtime CSS-in-JS solution to a build-time utility framework like Tailwind drastically improves performance but poses high visual regression risks. The architect isolates the migration. They configure the build system to support both paradigms simultaneously. They mandate that all *new* components strictly use Tailwind. For legacy components, they utilize automated AST (Abstract Syntax Tree) scripts to translate standard CSS rules into Tailwind classes. To prevent collisions, legacy CSS is strictly scoped using CSS Modules or unique hashing until it is fully decommissioned. Real-World Scenario: During the migration, a legacy `Button.js` using styled-components sits next to a new `Card.js` using Tailwind. Because the architect ensured the styled-components generate unique, hashed class names (e.g., `.sc-bdfBwQ`), the global Tailwind utility classes never accidentally bleed into or overwrite the legacy button’s layout. The application remains visually identical while the underlying tech debt is methodically erased.
Q39
How do you manage the risk of upgrading a major React version (e.g., v16 to v19) in an enterprise codebase with dozens of deprecated lifecycle methods?
The Core Architectural Concept: Utilizing Strict Mode isolation, automated codemods, and canary deployments. The Why & How: Upgrading React breaks apps relying on legacy string refs or `UNSAFE_componentWillMount`. The architect does not perform a blind upgrade. First, they enable `` strictly on newly developed layout trees to identify legacy violations in isolation. They execute official React codemods (via `jscodeshift`) to automatically rename unsafe lifecycles across the repo. Finally, they upgrade the core version in a long-lived integration branch and deploy it to a “canary” staging environment, running comprehensive E2E tests to catch obscure rendering edge cases before merging to main. Real-World Scenario: A massive financial app contains 300 class components. The architect runs a script that automatically wraps them in the `UNSAFE_` prefix. This satisfies the new React compiler, allowing the app to successfully boot in React 18. The team now benefits immediately from modern features like concurrent rendering for new code, while organizing a tech-debt backlog to slowly refactor the 300 classes to functional components with hooks over the next year.
Q40
When phasing out a REST API in favor of GraphQL, how do you architect the frontend transition without requiring the backend team to stop their work?
The Core Architectural Concept: The BFF (Backend-for-Frontend) pattern utilizing an Apollo Gateway or a lightweight Node middleware layer. The Why & How: The frontend cannot wait a year for the backend to rewrite 500 REST endpoints into a native GraphQL schema. The architect implements a Node.js BFF layer sitting between the React app and the legacy REST APIs. The frontend team builds a GraphQL schema on this BFF. When the React app queries GraphQL, the BFF resolvers internally execute the HTTP requests to the legacy REST endpoints, format the data, and return it. Real-World Scenario: The React team wants to fetch a User and their recent Orders in a single request, but the legacy backend requires three separate REST calls. The architect sets up an Apollo Server BFF. The React app sends one GraphQL query. The BFF handles the orchestration, makes the three REST calls, and stitches the response together. Later, when the backend team finally builds a native database-level GraphQL API, the frontend simply points their client to the new URL; the React components themselves do not need to change a single line of code.

9. Enterprise QA, Security & Observability

Q41
As an architect, how do you define the Testing Pyramid for a massive React application to balance confidence with CI/CD velocity?
The Core Architectural Concept: Maximizing ROI by concentrating heavily on Integration Tests (RTL) while strictly limiting brittle UI-driven End-to-End (E2E) tests. The Why & How: A CI pipeline that takes 2 hours to run E2E tests destroys developer velocity. The architect mandates a strict pyramid. The base consists of lightning-fast unit tests for pure functions and reducers. The massive middle layer utilizes React Testing Library with Mock Service Worker (MSW) to test complex component behaviors and API interactions entirely within JSDOM (executing in milliseconds). The peak contains a highly restricted number of E2E tests (Cypress/Playwright) that only test critical business flows (e.g., Login, Checkout) on a real browser against a staging database. Real-World Scenario: A developer builds a complex multi-step wizard. Instead of writing a Playwright script that spins up a Chrome browser to test every error validation state (which takes 45 seconds), they write 20 RTL tests simulating user clicks and keyboard inputs in Node (which takes 2 seconds). Playwright is only used to verify that the final “Submit” button successfully writes to the actual database.
Q42
How do you architect Contract Testing to ensure independent deployments of React frontends don’t break when microservices change their API payloads?
The Core Architectural Concept: Consumer-Driven Contract Testing (e.g., using Pact) to enforce schema alignment at build time. The Why & How: If a backend team renames the `user_id` field to `userId`, the React app will fail silently in production. Relying on E2E tests to catch this is too late and too slow. The architect implements Contract Testing. The React team defines a “contract” (a JSON file) explicitly stating the shape of the data they expect from the API. During the backend team’s CI/CD pipeline, their code is automatically tested against the React team’s contract. If the backend changes a field name, their own build fails instantly, preventing the breaking change from deploying. Real-World Scenario: The billing team decides to nest the `amount` field inside a `currency` object. When they push their PR, the Pact broker intercepts the build. It runs the backend response against the frontend’s expected contract. The build fails with the error: “Consumer ‘React-Dashboard’ expects top-level field ‘amount'”. The backend team is forced to version their API or collaborate with the frontend team to update the contract, guaranteeing production stability.
Q43
What is your architectural approach to Shift-Left Performance Testing to prevent slow components from ever reaching production?
The Core Architectural Concept: Integrating automated bundle auditing and Lighthouse CI directly into the pull request pipeline as strict deployment gates. The Why & How: Performance degrades organically as developers import heavy libraries (like `lodash` or `moment.js`) without realizing the bundle impact. Fixing this in production is reactive. The architect implements tools like `bundlesize` or Webpack Bundle Analyzer into the CI pipeline. If a PR increases the master JS bundle size by more than 2%, or if the Lighthouse CI score drops below 90, the PR is automatically marked with a red X and cannot be merged without explicit architect approval. Real-World Scenario: A junior developer imports the entire `echarts` library to draw a simple pie chart, inflating the bundle by 800kb. When they open a PR, the CI bot automatically comments: “Bundle size threshold exceeded. Baseline: 2MB. PR: 2.8MB.” The merge button is disabled. The developer is forced to research tree-shaking and dynamically import only the specific chart module, resolving the issue before it ever impacts a user’s browser.
Q44
How do you architect a Feature Flag (Toggle) system that scales without littering the React codebase with thousands of messy IF statements?
The Core Architectural Concept: Decoupling the flag evaluation logic from the UI rendering layer using Higher-Order Components or dedicated wrapper components connected to a centralized Context. The Why & How: Sprinkling `if (featureFlags.newCheckout)` across 50 different components creates massive tech debt when it’s time to remove the flag. The architect creates a centralized configuration system (often powered by LaunchDarkly). They mandate the use of a strict declarative component, such as `}> `. This keeps the domain logic completely blind to the existence of the flag. Real-World Scenario: The team builds a new AI-powered search bar. Instead of hacking the Header layout with ternary operators, they wrap the new search bar in the `` component. Once the A/B test is highly successful and fully rolled out, cleaning up the tech debt is trivial: a developer simply searches the codebase for ``, deletes the wrapper and the fallback prop, and the clean `` component remains.
Q45
What is your strategy for architecting secure Content Security Policies (CSP) for a React application that heavily utilizes external CDN assets and analytics?
The Core Architectural Concept: Implementing strict, nonces-based CSP headers via the edge server to prevent XSS and unauthorized data exfiltration, while allowing dynamic hydration. The Why & How: A weak CSP allows malicious scripts injected via XSS to execute or send data to attacker domains. The architect must generate a unique cryptographic `nonce` on the server for every single page load. This nonce is injected into the HTTP response header and applied to all legitimate script and `

React JS Interviw Question: The codeing challenge and machine round

100 Comprehensive React JS Interview Questions & Answers. Master everything from Beginner hooks to Expert-level React JS coding challenge and machine round interview.

Beginner Level

Q01
What is React? What are its core features?
Beginner

React is an open-source JavaScript library created by Facebook for building user interfaces, particularly single-page applications. Its core features are:

  • Component-based architecture — UI is split into reusable, self-contained pieces.
  • Virtual DOM — React keeps a lightweight in-memory copy of the real DOM and only updates what changed, boosting performance.
  • JSX — A syntax extension that lets you write HTML-like code inside JavaScript.
  • Unidirectional data flow — Data flows from parent to child via props, making apps easier to debug.
  • Hooks — Functions like useState and useEffect that add state and lifecycle behavior to functional components.
Q02
What is JSX and why do we use it?
Beginner

JSX (JavaScript XML) is a syntax extension that allows you to write HTML-like markup inside JavaScript files. It is not valid JavaScript — Babel transpiles it into React.createElement() calls at build time.

// JSX
const element = <h1 className="title">Hello, World!</h1>;

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

JSX makes component code more readable and easier to reason about compared to chained createElement calls.

Q03
What is the difference between a class component and a functional component?
Beginner
// Class Component
class Greeting extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// Functional Component (preferred)
function Greeting({ name }) {
  return <h1>Hello, {name}</h1>;
}

Functional components are simpler, use less boilerplate, and since React 16.8 can use Hooks for state and side-effects. Class components require this, lifecycle methods, and are generally more verbose. New code should prefer functional components.

Q04
What are props in React? How do you pass them?
Beginner

Props (short for properties) are read-only inputs passed from a parent component to a child component. They make components reusable by letting the parent control child behavior/appearance.

function Button({ label, color }) {
  return <button style={{ background: color }}>{label}</button>;
}

// Usage
<Button label="Submit" color="blue" />

Props are immutable inside the child — the child must never modify them directly.

Q05
What is state in React and how is it different from props?
Beginner

State is mutable data managed inside a component. When state changes, the component re-renders. Props are immutable data passed from outside.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Think of props as arguments to a function and state as local variables that persist across renders.

Q06
What is the Virtual DOM and how does React use it?
Beginner

The Virtual DOM is a lightweight JavaScript object tree that mirrors the real DOM. When state or props change, React:

  • Renders a new Virtual DOM tree.
  • Diffs it against the previous tree (reconciliation).
  • Computes the minimal set of real DOM mutations.
  • Applies only those changes to the actual browser DOM.

This batched, minimal-update strategy is far faster than naive full-page re-renders.

Q07
How does useState work? Give an example.
Beginner

useState is a Hook that adds local state to a functional component. It returns a tuple: the current value and a setter function.

import { useState } from 'react';

function Toggle() {
  const [isOn, setIsOn] = useState(false);

  return (
    <button onClick={() => setIsOn(prev => !prev)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

The functional updater form prev => !prev is preferred when the new value depends on the old one, because React may batch state updates.

Q08
What is useEffect and when do you use it?
Beginner

useEffect lets you perform side-effects (data fetching, subscriptions, DOM mutations, timers) after render. It runs after the component renders and optionally cleans up before re-running.

import { useState, useEffect } from 'react';

function UserCard({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(setUser);

    // cleanup (runs before next effect or unmount)
    return () => setUser(null);
  }, [userId]); // re-runs only when userId changes

  if (!user) return <p>Loading...</p>;
  return <p>{user.name}</p>;
}
Q09
What are React Hooks? Name five built-in Hooks.
Beginner

Hooks are functions that let functional components tap into React features that were previously only available in class components.

  • useState — local component state
  • useEffect — side-effects and lifecycle
  • useContext — consume a React context
  • useRef — mutable ref object / DOM access
  • useMemo — memoize expensive computed values
  • useCallback — memoize callback functions
  • useReducer — complex state with reducer pattern

Rules of Hooks: only call at the top level, only call inside React functions — never inside conditionals or loops.

Q10
What is the purpose of the key prop in lists?
Beginner

The key prop helps React identify which items in a list have changed, been added, or removed during reconciliation. Keys must be stable, unique among siblings, and ideally come from your data (e.g., database IDs).

const items = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];

function List() {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li> // ✅ stable, unique
      ))}
    </ul>
  );
}

Avoid using array index as key when the list can be reordered — this causes subtle re-render bugs.

Q11
What is conditional rendering in React?
Beginner

Conditional rendering lets you show or hide UI based on state or props. Common patterns:

function Alert({ isError, message }) {
  // if/else
  if (isError) return <div className="error">{message}</div>;

  // ternary
  return <div>{message ? message : 'No messages'}</div>;

  // short-circuit &&
  return <div>{message && <span>{message}</span>}</div>;
}
Q12
How do you handle events in React?
Beginner

React events use camelCase names and receive a SyntheticEvent — a cross-browser wrapper around the native event.

function Form() {
  function handleSubmit(e) {
    e.preventDefault(); // prevent page reload
    console.log('submitted');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={e => console.log(e.target.value)} />
      <button type="submit">Send</button>
    </form>
  );
}
Q13
What is React.Fragment and why is it useful?
Beginner

Fragments let you group multiple elements without adding an extra DOM node. This avoids invalid HTML (e.g., a <tr> inside a <div>) and keeps the DOM clean.

// Short syntax
function Columns() {
  return (
    <>
      <td>Name</td>
      <td>Age</td>
    </>
  );
}

// With key (must use long form)
items.map(item => (
  <React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.def}</dd>
  </React.Fragment>
))
Q14
What is useRef? Give two use cases.
Beginner

useRef returns a mutable object { current: value } that persists across renders without causing re-renders when changed.

// Use case 1: access a DOM node
function FocusInput() {
  const inputRef = useRef(null);
  return (
    <>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

// Use case 2: store a mutable value (e.g. previous state)
function Timer() {
  const timerIdRef = useRef(null);
  const start = () => { timerIdRef.current = setInterval(tick, 1000); };
  const stop  = () => clearInterval(timerIdRef.current);
  // ...
}
Q15
What is prop drilling and what problems does it cause?
Beginner

Prop drilling happens when you must pass data through many intermediate components just to reach a deeply nested consumer that actually needs it.

// theme has to travel A → B → C even though B doesn't use it
<A theme="dark" />
  <B theme={theme} />    // B just passes it down
    <C theme={theme} />  // C actually uses it

Problems: tight coupling, verbose code, hard to refactor. Solutions include React Context, Redux, Zustand, or component composition.

Q16
What is React Context and how do you use it?
Beginner

Context provides a way to share values (theme, auth, locale) across the component tree without prop drilling.

const ThemeContext = React.createContext('light');

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

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>Toolbar</div>;
}

Context should be used for low-frequency global data. For high-frequency updates, prefer a state manager to avoid unnecessary re-renders.

Q17
How do you lift state up in React?
Beginner

When two sibling components need to share state, you lift the state to their closest common ancestor and pass it down as props.

function Parent() {
  const [value, setValue] = useState('');
  return (
    <>
      <Input value={value} onChange={setValue} />
      <Display value={value} />
    </>
  );
}

function Input({ value, onChange }) {
  return <input value={value} onChange={e => onChange(e.target.value)} />;
}

function Display({ value }) {
  return <p>{value}</p>;
}
Q18
What are controlled vs uncontrolled components?
Beginner

A controlled component has its form input value driven by React state. A uncontrolled component manages its own state internally via the DOM; you read the value with a ref.

// Controlled
const [text, setText] = useState('');
<input value={text} onChange={e => setText(e.target.value)} />

// Uncontrolled
const inputRef = useRef();
<input ref={inputRef} defaultValue="hello" />
// read: inputRef.current.value

Controlled components give you full control over validation and transformations on every keystroke. Uncontrolled components are simpler for basic forms and integrating with non-React code.

Q19
What is React.StrictMode?
Beginner

StrictMode is a developer tool that helps you spot potential problems. It intentionally double-invokes render functions, state initializers, and effects (in development) to surface side-effects written incorrectly. It has no effect in production builds.

<React.StrictMode>
  <App />
</React.StrictMode>

Warnings it catches: deprecated API usage, impure render side-effects, unexpected re-render issues, and missing cleanup in effects.

Q20
How do you update an object or array in state correctly?
Beginner

State must be treated as immutable. Always return a new object/array instead of mutating the existing one.

// ❌ Wrong — mutates directly
state.user.name = 'Alice'; setState(state);

// ✅ Correct — spread into new object
setState(prev => ({ ...prev, user: { ...prev.user, name: 'Alice' } }));

// ✅ Array: add item
setItems(prev => [...prev, newItem]);

// ✅ Array: remove item
setItems(prev => prev.filter(item => item.id !== targetId));

// ✅ Array: update item
setItems(prev => prev.map(item => item.id === targetId ? { ...item, done: true } : item));
Q21
What is the difference between null and undefined rendering in JSX?
Beginner

Both null, undefined, and false render nothing — they are valid children that produce no DOM output. This makes them ideal for conditional rendering.

function Component({ show }) {
  return (
    <div>
      {show && <p>Visible!</p>}  // nothing rendered when show=false
      {null}                            // nothing rendered
      {0}                               // ⚠️ renders "0"! be careful
    </div>
  );
}

Note: the number 0 does render — a common footgun when using count && <Comp />.

Q22
What is default props and how do you set it?
Beginner
// Modern: destructuring defaults
function Button({ label = 'Click me', color = 'blue' }) {
  return <button style={{ color }}>{label}</button>;
}

// Legacy: static property
Button.defaultProps = { label: 'Click me', color: 'blue' };

Destructuring defaults are preferred in modern React since defaultProps may be removed in a future major version.

Q23
What is children prop and how is it used?
Beginner

The children prop contains everything placed between the component’s opening and closing tags, enabling composable wrapper components.

function Card({ children, title }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

// Usage
<Card title="Hello">
  <p>I am a child!</p>
</Card>
Q24
How do you apply inline styles in React?
Beginner

In React, the style attribute accepts a JavaScript object with camelCased property names and string values (not a CSS string).

const styles = {
  backgroundColor: '#0d1117',
  fontSize: '16px',
  marginTop: 8,          // numbers default to px
  fontWeight: 'bold',
};

<div style={styles}>Styled</div>
// or inline:
<div style={{ color: 'red' }}>Red</div>
Q25
What happens when you call setState multiple times in a row?
Beginner

React batches multiple state updates in event handlers (and in React 18+, everywhere including async code) into a single re-render for performance.

function Component() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);

  function handleClick() {
    setA(1); // batched
    setB(2); // batched
    // → only ONE re-render occurs
  }
}

If you need the current state value based on the previous update, use the functional updater form: setCount(prev => prev + 1).

Intermediate Level

Q26
What is useReducer and when should you use it over useState?
Intermediate

useReducer is ideal when state transitions depend on the previous state and multiple sub-values change together, or when the next state logic is complex.

const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { ...state, count: state.count + state.step };
    case 'setStep':   return { ...state, step: action.payload };
    default: throw new Error('Unknown action');
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}
Q27
What is useMemo and when should you use it?
Intermediate

useMemo memoizes the result of an expensive computation, recomputing it only when dependencies change. It prevents unnecessary recalculations on every render.

import { useMemo } from 'react';

function ProductList({ products, filter }) {
  const filtered = useMemo(
    () => products.filter(p => p.category === filter),
    [products, filter]  // only recompute when these change
  );

  return filtered.map(p => <ProductCard key={p.id} product={p} />);
}

Don’t over-optimize — only use useMemo when a profiler shows a real bottleneck. The memoization itself has overhead.

Q28
What is useCallback and how does it differ from useMemo?
Intermediate

useCallback(fn, deps) memoizes a function reference. It’s equivalent to useMemo(() => fn, deps). Use it when passing callbacks to memoized child components to prevent unnecessary re-renders.

const handleClick = useCallback(() => {
  doSomethingWith(id);
}, [id]); // stable reference unless id changes

// useMemo — memoizes a VALUE
const total = useMemo(() => items.reduce((s, i) => s + i.price, 0), [items]);

// useCallback — memoizes a FUNCTION
const getTotal = useCallback(() => items.reduce((s, i) => s + i.price, 0), [items]);
Q29
What is React.memo? How does it work?
Intermediate

React.memo is a higher-order component that memoizes a functional component. It skips re-rendering if props haven’t changed (shallow comparison).

const ExpensiveChild = React.memo(function({ value }) {
  console.log('rendered');
  return <div>{value}</div>;
});

// Custom comparator
const MemoComp = React.memo(Comp, (prev, next) => {
  return prev.id === next.id; // return true → skip re-render
});

Works best when paired with useCallback/useMemo for stable prop references.

Q30
What is a custom Hook? Write one that fetches data.
Intermediate

A custom Hook is a function whose name starts with use and that calls other Hooks. It extracts reusable stateful logic from components.

function useFetch(url) {
  const [data, setData]   = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

  return { data, loading, error };
}

// Usage
const { data, loading } = useFetch('/api/users');
Q31
What is reconciliation in React?
Intermediate

Reconciliation is the algorithm React uses to diff the new Virtual DOM tree against the previous one and determine the minimal set of real DOM changes needed.

Key heuristics:

  • Elements of different types produce entirely different trees (full subtree rebuild).
  • The developer can hint stable identity with the key prop.
  • Same type → React updates props in place, keeping DOM node and children.

React 18 uses the Fiber architecture which makes reconciliation interruptible, enabling concurrent features like Suspense and transitions.

Q32
What is React Fiber?
Intermediate

Fiber is React’s internal reconciliation engine (introduced in React 16). It reimplements the reconciler using a linked list of “fiber” units of work, allowing React to pause, resume, abort, and prioritize rendering work.

This enables:

  • Concurrent rendering — interruptible renders that keep the UI responsive.
  • Suspense & lazy loading — pause rendering until async data or components are ready.
  • startTransition — mark non-urgent state updates so urgent updates (typing) stay fast.
Q33
What is React.lazy and Suspense? Write an example.
Intermediate

React.lazy lets you code-split a component into a separate bundle loaded on demand. Suspense shows a fallback while it loads.

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Dashboard />
    </Suspense>
  );
}

The browser only downloads the Dashboard bundle when it’s first rendered. Useful for route-level code splitting.

Q34
What are Error Boundaries? How do you create one?
Intermediate

Error Boundaries are class components that catch JavaScript errors in their child tree and display a fallback UI instead of crashing the whole app. They must implement static getDerivedStateFromError or componentDidCatch.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    logErrorToService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError)
      return <h2>Something went wrong.</h2>;
    return this.props.children;
  }
}

Note: Error boundaries don’t catch errors in event handlers or async code — use regular try/catch for those.

Q35
What is the useLayoutEffect Hook? How is it different from useEffect?
Intermediate

useLayoutEffect fires synchronously after all DOM mutations but before the browser paints. useEffect fires after the paint.

useLayoutEffect(() => {
  // Measure DOM, synchronously update layout
  const rect = ref.current.getBoundingClientRect();
  setWidth(rect.width);
}); // no flicker — runs before browser paint

Use useLayoutEffect when you need to read layout from the DOM and synchronously re-render to prevent visual flicker (e.g., tooltips, measuring elements). For everything else, prefer useEffect to avoid blocking the paint.

Q36
What is the React Portals API and when would you use it?
Intermediate

Portals render children into a DOM node that exists outside the parent component’s DOM hierarchy, while still keeping them in the React component tree (events bubble normally).

import { createPortal } from 'react-dom';

function Modal({ children }) {
  return createPortal(
    <div className="modal">{children}</div>,
    document.getElementById('modal-root')  // DOM outside app root
  );
}

Common use cases: modals, tooltips, dropdowns — anything that needs to visually escape overflow-hidden or z-index constraints of its parent.

Q37
What is forwardRef and why is it needed?
Intermediate

By default, ref cannot be passed as a prop to a functional component. forwardRef lets you expose a ref from a parent to a DOM node inside the child.

const Input = React.forwardRef((props, ref) => (
  <input ref={ref} {...props} />
));

function Parent() {
  const inputRef = useRef();
  return <Input ref={inputRef} />; // ref reaches the <input> DOM node
}

Commonly used in design system libraries to give consumers direct DOM access while keeping internal implementation details abstracted.

Q38
What is the difference between useEffect with no deps, empty array [], and dependencies?
Intermediate
// No dependency array → runs after EVERY render
useEffect(() => { console.log('every render'); });

// Empty array [] → runs ONCE after mount
useEffect(() => { console.log('mounted'); }, []);

// With deps → runs on mount AND when any dep changes
useEffect(() => {
  console.log('userId changed');
}, [userId]);

The cleanup function returned from useEffect runs before the next effect execution or on unmount — in all three cases.

Q39
How does React handle forms? Build a simple validated form.
Intermediate
function LoginForm() {
  const [fields, setFields] = useState({ email: '', password: '' });
  const [errors, setErrors] = useState({});

  function validate() {
    const e = {};
    if (!fields.email.includes('@')) e.email = 'Invalid email';
    if (fields.password.length < 8) e.password = 'Min 8 chars';
    return e;
  }

  function handleSubmit(e) {
    e.preventDefault();
    const e2 = validate();
    if (Object.keys(e2).length) { setErrors(e2); return; }
    submitToServer(fields);
  }

  const change = field => e =>
    setFields(prev => ({ ...prev, [field]: e.target.value }));

  return (
    <form onSubmit={handleSubmit}>
      <input value={fields.email} onChange={change('email')} />
      {errors.email && <span>{errors.email}</span>}
      <input type="password" value={fields.password} onChange={change('password')} />
      {errors.password && <span>{errors.password}</span>}
      <button type="submit">Login</button>
    </form>
  );
}
Q40
What is the Render Props pattern?
Intermediate

The render props pattern involves a component that accepts a function as a prop (or as children), and calls it to determine what to render, sharing logic without inheritance or HOCs.

function MouseTracker({ render }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  return (
    <div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
      {render(pos)}
    </div>
  );
}

// Usage
<MouseTracker render={({ x, y }) => <p>{x}, {y}</p>} />

Hooks have largely replaced render props for sharing logic, but the pattern is still common in libraries like React Router and Formik.

Q41
What is a Higher-Order Component (HOC)?
Intermediate

A HOC is a function that takes a component and returns an enhanced component. It’s a compositional pattern for cross-cutting concerns (auth, logging, theming).

function withAuth(WrappedComponent) {
  return function AuthGuard(props) {
    const { isLoggedIn } = useAuth();
    if (!isLoggedIn) return <Redirect to="/login" />;
    return <WrappedComponent {...props} />;
  };
}

const ProtectedDashboard = withAuth(Dashboard);

HOCs should not mutate the wrapped component. Use a display name (AuthGuard.displayName) for better DevTools debugging.

Q42
Explain the Context + useReducer pattern for global state.
Intermediate

Combining Context and useReducer gives you a lightweight Redux-like global store without external libraries.

const StoreContext = createContext();

function storeReducer(state, action) {
  switch (action.type) {
    case 'LOGIN':  return { ...state, user: action.payload };
    case 'LOGOUT': return { ...state, user: null };
    default: return state;
  }
}

export function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(storeReducer, { user: null });
  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
}

export const useStore = () => useContext(StoreContext);
Q43
What is useImperativeHandle and when do you use it?
Intermediate

useImperativeHandle customizes the instance value exposed to parent refs via forwardRef, allowing you to expose only a limited API instead of the raw DOM node.

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current.focus(),
    clear: () => { inputRef.current.value = ''; },
    // DOM node itself is NOT exposed
  }));

  return <input ref={inputRef} />;
});

// Parent can call: ref.current.focus() or ref.current.clear()
Q44
What is React Router? How do you set up basic routing?
Intermediate
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/user/42">User</Link>
      </nav>
      <Routes>
        <Route path="/"          element={<Home />}         />
        <Route path="/about"     element={<About />}        />
        <Route path="/user/:id"  element={<UserProfile />}  />
        <Route path="*"          element={<NotFound />}      />
      </Routes>
    </BrowserRouter>
  );
}
Q45
How do you fetch data and handle loading/error states?
Intermediate
function PostList() {
  const [posts, setPosts]   = useState([]);
  const [status, setStatus] = useState('idle'); // idle|loading|success|error
  const [error, setError]   = useState(null);

  useEffect(() => {
    setStatus('loading');
    fetch('/api/posts')
      .then(r => { if (!r.ok) throw new Error(r.statusText); return r.json(); })
      .then(data => { setPosts(data); setStatus('success'); })
      .catch(err => { setError(err.message); setStatus('error'); });
  }, []);

  if (status === 'loading') return <Spinner />;
  if (status === 'error')   return <p>Error: {error}</p>;
  return posts.map(p => <Post key={p.id} post={p} />);
}
Q46
What is the difference between React.cloneElement and children props?
Intermediate

React.cloneElement lets you clone a React element and inject additional props or override existing ones, useful in compound component patterns.

function Tabs({ children, activeTab }) {
  return (
    <div>
      {React.Children.map(children, child =>
        React.cloneElement(child, {
          isActive: child.props.id === activeTab
        })
      )}
    </div>
  );
}
// Each Tab child now receives isActive without the parent knowing its internals

Modern alternative: use Context to share state inside compound components without cloneElement.

Q47
How do you debounce a search input in React?
Intermediate
function Search() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!query) { setResults([]); return; }
    const timer = setTimeout(() => {
      fetchResults(query).then(setResults);
    }, 300); // 300ms debounce

    return () => clearTimeout(timer); // cancel if query changes
  }, [query]);

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      {results.map(r => <div key={r.id}>{r.title}</div>)}
    </>
  );
}
Q48
What are React DevTools and how do you use them for profiling?
Intermediate

React DevTools is a browser extension that adds a Components and Profiler panel to browser DevTools.

  • Components panel — inspect the component tree, view props/state/hooks, and highlight re-renders.
  • Profiler panel — record a session, then see which components rendered, how long each took (in ms), and why they re-rendered. Flame chart shows the render waterfall.

Workflow: Record → interact with the app → stop recording → look for unexpectedly frequent or slow renders → apply memo, useCallback, or structural fixes as needed.

Q49
What is the compound component pattern?
Intermediate

Compound components are a set of components that work together and share implicit state via Context. The parent manages state; children can access it without explicit props.

const AccordionContext = createContext();

function Accordion({ children }) {
  const [open, setOpen] = useState(null);
  return (
    <AccordionContext.Provider value={{ open, setOpen }}>
      <div>{children}</div>
    </AccordionContext.Provider>
  );
}

function Item({ id, children }) {
  const { open, setOpen } = useContext(AccordionContext);
  return (
    <div>
      <button onClick={() => setOpen(open === id ? null : id)}>Toggle</button>
      {open === id && children}
    </div>
  );
}

Accordion.Item = Item;

// Usage: <Accordion><Accordion.Item id="a">...</Accordion.Item></Accordion>
Q50
How do you implement infinite scroll in React?
Intermediate
function InfiniteList() {
  const [items, setItems]   = useState([]);
  const [page, setPage]     = useState(1);
  const sentinelRef         = useRef();

  useEffect(() => {
    fetchPage(page).then(data => setItems(prev => [...prev, ...data]));
  }, [page]);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) setPage(p => p + 1);
    });
    if (sentinelRef.current) observer.observe(sentinelRef.current);
    return () => observer.disconnect();
  }, []);

  return (
    <>
      {items.map(i => <Item key={i.id} data={i} />)}
      <div ref={sentinelRef} /> // invisible bottom sentinel
    </>
  );
}
Q51
What is code splitting and how do you do it in React?
Intermediate

Code splitting breaks your bundle into smaller chunks loaded on demand, reducing initial bundle size and TTI (Time to Interactive).

  • Component-level: React.lazy + dynamic import()
  • Route-level: Lazy-load each route component
  • Library-level: Webpack/Vite automatically split node_modules
// Route-level splitting
const Home    = lazy(() => import('./routes/Home'));
const Profile = lazy(() => import('./routes/Profile'));

<Suspense fallback={<Spinner/>}>
  <Routes>
    <Route path="/"        element={<Home/>}    />
    <Route path="/profile" element={<Profile/>} />
  </Routes>
</Suspense>
Q52
How does React handle accessibility (a11y)?
Intermediate

React supports full HTML accessibility attributes with camelCase naming. Key practices:

// aria-* attributes stay hyphenated
<button aria-label="Close modal" aria-expanded={isOpen}>✕</button>

// for/htmlFor association
<label htmlFor="email">Email</label>
<input id="email" type="email" />

// Focus management for modals
useEffect(() => { if (isOpen) closeButtonRef.current?.focus(); }, [isOpen]);

Tools: eslint-plugin-jsx-a11y, React Aria (Adobe), axe-core DevTools extension.

Q53
What is the useId Hook?
Intermediate

Introduced in React 18, useId generates a stable unique ID that is consistent between server and client renders — avoiding SSR hydration mismatches.

function FormField({ label }) {
  const id = useId(); // e.g. ":r1:"
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

Do not use useId to generate keys for lists — use data IDs for that.

Q54
What are transitions in React 18?
Intermediate

startTransition marks a state update as non-urgent. React will defer it and keep the UI responsive for urgent updates (like typing).

import { startTransition, useTransition } from 'react';

function Search() {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  function handleChange(e) {
    setQuery(e.target.value); // urgent — update input immediately
    startTransition(() => {
      setResults(computeResults(e.target.value)); // non-urgent
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner/> : results.map(...)}
    </>
  );
}
Q55
How do you test React components? What tools do you use?
Intermediate

The standard stack: Vitest or Jest (test runner) + React Testing Library (RTL) for DOM-focused tests + Playwright/Cypress for end-to-end tests.

// Example RTL test
import { render, screen, fireEvent } from '@testing-library/react';

test('increments counter', () => {
  render(<Counter />);
  expect(screen.getByText('Count: 0')).toBeInTheDocument();
  fireEvent.click(screen.getByRole('button', { name: /increment/i }));
  expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

RTL philosophy: test what the user sees (text, roles) not implementation details (state, refs).

Advanced Level

Q56
Explain React’s concurrent rendering and its benefits.
Advanced

Concurrent rendering (React 18) lets React prepare multiple versions of the UI simultaneously without blocking the main thread. Renders can be interrupted, paused, and resumed based on priority.

Key APIs:

  • createRoot — opt into concurrent mode
  • startTransition / useTransition — deprioritize non-urgent updates
  • useDeferredValue — defer a value to avoid blocking input
  • Suspense + data fetching — show fallbacks while async rendering

Benefit: heavy renders (large lists, complex charts) no longer freeze the UI — React keeps urgent interactions (typing, clicking) buttery smooth.

Q57
What is useDeferredValue? How does it compare to debouncing?
Advanced

useDeferredValue accepts a value and returns a deferred version that “lags behind” to allow more urgent renders to go first.

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  // stale deferredQuery during typing → React renders latest query first
  const results = expensiveFilter(deferredQuery);
  return results.map(r => <Result key={r.id} {...r} />);
}

vs debounce: debounce delays state updates on a fixed timer. useDeferredValue lets React schedule the update based on available CPU time — no artificial delay, and it starts updating as soon as the browser is idle.

Q58
What is Server-Side Rendering (SSR) with React and how does it work?
Advanced

With SSR, React renders components to HTML on the server and sends it to the browser. The client then “hydrates” — attaching event listeners to the existing HTML without re-rendering.

// Server (Node.js)
import { renderToString } from 'react-dom/server';
const html = renderToString(<App />);
res.send(`<html><body><div id="root">${html}</div></body></html>`);

// Client
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);

Benefits: faster FCP, SEO-friendly. Drawbacks: TTFB increases, server load, hydration complexity. Frameworks: Next.js, Remix.

Q59
What are React Server Components (RSC)?
Advanced

React Server Components (introduced in React 18, popularized by Next.js 13+ App Router) run exclusively on the server. They can access databases and file systems directly, never ship JS to the client, and reduce bundle size.

  • Server Components — async, no state/hooks, zero client JS
  • Client Components'use client' directive, can use hooks and events
  • Shared Components — can render as either depending on where they’re imported
// app/page.tsx — Server Component (default in Next.js 13+)
async function Page() {
  const data = await db.query('SELECT * FROM posts'); // runs on server only
  return data.map(post => <PostCard key={post.id} post={post} />);
}
Q60
How does hydration work and what are hydration errors?
Advanced

Hydration is the process of attaching React’s event system to server-rendered HTML. React walks the existing DOM and matches it against the Virtual DOM tree. If they don’t match, React throws a hydration error and falls back to client rendering.

Common causes of hydration mismatch:

  • Rendering Date.now() or Math.random() differently server vs client
  • Using typeof window to conditionally render
  • Third-party scripts modifying the DOM before React hydrates
  • Invalid HTML nesting (e.g. <p><div></div></p>)

Fix: use suppressHydrationWarning for intentional mismatches (e.g., timestamps), or defer rendering until client with useEffect.

Q61
What is the Suspense data-fetching model (Suspense for Data Fetching)?
Advanced

A component “suspends” by throwing a Promise. React catches it, shows the nearest Suspense fallback, and retries rendering the component when the Promise resolves.

// Library creates a "resource" that throws a Promise
function wrapPromise(promise) {
  let status = 'pending', result;
  const p = promise.then(d => { status = 'success'; result = d; })
                   .catch(e => { status = 'error'; result = e; });
  return { read() {
    if (status === 'pending')  throw p;
    if (status === 'error')    throw result;
    return result;
  }};
}

// Component using the resource
function UserProfile({ resource }) {
  const user = resource.read(); // throws Promise if not ready
  return <div>{user.name}</div>;
}

In practice, frameworks like Next.js and libraries like TanStack Query implement this for you.

Q62
Build a virtualized list from scratch (windowing).
Advanced

Virtualization renders only the visible rows, keeping DOM nodes constant regardless of list size. Great for lists of 10,000+ items.

const ROW_HEIGHT = 40;

function VirtualList({ items }) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerHeight = 400;
  const totalHeight = items.length * ROW_HEIGHT;

  const startIndex = Math.floor(scrollTop / ROW_HEIGHT);
  const visibleCount = Math.ceil(containerHeight / ROW_HEIGHT) + 1;
  const visibleItems = items.slice(startIndex, startIndex + visibleCount);

  return (
    <div
      style={{ height: containerHeight, overflowY: 'auto', position: 'relative' }}
      onScroll={e => setScrollTop(e.target.scrollTop)}
    >
      <div style={{ height: totalHeight }}>
        {visibleItems.map((item, i) => (
          <div
            key={item.id}
            style={{
              position: 'absolute',
              top: (startIndex + i) * ROW_HEIGHT,
              height: ROW_HEIGHT,
            }}
          >
            {item.name}
          </div>
        ))}
      </div>
    </div>
  );
}

In production, use react-window or @tanstack/react-virtual.

Q63
What is the Flux architecture? How does it relate to Redux?
Advanced

Flux is a unidirectional data-flow pattern from Facebook: Action → Dispatcher → Store → View → Action. Redux is an opinionated Flux implementation with a single store, pure reducer functions, and a rich middleware ecosystem.

// Redux flow
store.dispatch({ type: 'counter/increment' }); // Action
// Reducer: (state, action) => newState
// Subscribers re-render

Redux Toolkit (RTK) is now the official, recommended way to use Redux — it uses Immer internally so you can “mutate” state in reducers, and createSlice handles action type boilerplate.

Q64
What is Zustand and how does it compare to Redux?
Advanced

Zustand is a minimal, hook-based state manager. It has almost no boilerplate and works outside React components too.

import { create } from 'zustand';

const useStore = create(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
}));

function Counter() {
  const { count, increment } = useStore();
  return <button onClick={increment}>{count}</button>;
}

vs Redux: Zustand is far less boilerplate, no Provider needed, subscribes components to only the slice of state they use. Redux RTK remains better for large teams needing strict conventions, time-travel debugging, and powerful middleware.

Q65
How do you optimize a React app that re-renders too frequently?
Advanced

Systematic approach:

  • Profile first — use React DevTools Profiler to find offending components before guessing.
  • Memoize componentsReact.memo skips re-renders when props are reference-equal.
  • Stable referencesuseCallback / useMemo prevent new object/function refs on each render.
  • Split Context — separate frequently-changing from infrequently-changing context values.
  • Colocate state — push state down; only subtrees that need it re-render.
  • Virtualize listsreact-window renders only visible rows.
  • Lazy load — code-split heavy sections, images, data.
  • Transitions — wrap non-urgent updates in startTransition.
Q66
What is flushSync in React 18?
Advanced

React 18 batches all state updates automatically (even in setTimeout and Promises). flushSync forces React to flush pending updates synchronously inside the callback — useful when you need DOM measurements immediately after a state update.

import { flushSync } from 'react-dom';

flushSync(() => {
  setItems([..items, newItem]);
});
// DOM is updated HERE, before the next line
listRef.current.lastChild.scrollIntoView();

Use sparingly — overuse hurts performance by defeating batching.

Q67
Implement a generic drag-and-drop list in React.
Advanced
function DnDList({ initialItems }) {
  const [items, setItems] = useState(initialItems);
  const dragIndex = useRef(null);

  function handleDragStart(index) { dragIndex.current = index; }

  function handleDrop(dropIndex) {
    const updated = [...items];
    const [removed] = updated.splice(dragIndex.current, 1);
    updated.splice(dropIndex, 0, removed);
    setItems(updated);
    dragIndex.current = null;
  }

  return (
    <ul>
      {items.map((item, i) => (
        <li
          key={item.id}
          draggable
          onDragStart={() => handleDragStart(i)}
          onDragOver={e => e.preventDefault()}
          onDrop={() => handleDrop(i)}
        >
          {item.label}
        </li>
      ))}
    </ul>
  );
}

For production: use @dnd-kit/core or react-beautiful-dnd for accessibility, touch support, and animation.

Q68
What is the stale closure problem in React Hooks?
Advanced

A stale closure occurs when a callback captures an old value from a previous render and doesn’t see the current state/props.

// ❌ Bug — count is stale inside setInterval callback
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1); // always reads count=0
  }, 1000);
  return () => clearInterval(id);
}, []); // empty deps — effect never re-runs

// ✅ Fix — use functional updater
setCount(prev => prev + 1); // prev is always fresh

// ✅ Alternative — use a ref to track latest value
const countRef = useRef(count);
countRef.current = count;
// inside callback: use countRef.current
Q69
How do you implement optimistic updates in React?
Advanced

An optimistic update applies a change immediately in the UI before the server confirms it, then rolls back if the server returns an error.

async function toggleLike(postId) {
  // 1. Optimistically update UI
  setPosts(prev => prev.map(p =>
    p.id === postId ? { ...p, liked: !p.liked } : p
  ));

  try {
    await api.toggleLike(postId); // 2. Persist on server
  } catch {
    // 3. Roll back on failure
    setPosts(prev => prev.map(p =>
      p.id === postId ? { ...p, liked: !p.liked } : p // toggle back
    ));
    toast.error('Failed to update like');
  }
}

React 19 introduced useOptimistic for a built-in, first-class API for this pattern.

Q70
What is TanStack Query (React Query) and what problems does it solve?
Advanced

TanStack Query is a server-state management library. It handles: caching, background refetching, deduplication of requests, pagination, infinite scroll, optimistic updates, synchronization, and loading/error states — all declaratively.

import { useQuery, useMutation } from '@tanstack/react-query';

function Posts() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json()),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  if (isLoading) return <Spinner />;
  if (error) return <Error />;
  return data.map(p => <Post key={p.id} post={p} />);
}
Q71
How do you implement a real-time feature (e.g. live notifications) in React?
Advanced
function useNotifications(userId) {
  const [notifications, setNotifications] = useState([]);

  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/ws?user=${userId}`);

    ws.onmessage = (event) => {
      const notif = JSON.parse(event.data);
      setNotifications(prev => [notif, ...prev]);
    };

    ws.onerror  = (e) => console.error('WebSocket error', e);
    ws.onclose  = ()  => console.log('WebSocket closed');

    return () => ws.close(); // cleanup on unmount / userId change
  }, [userId]);

  return notifications;
}

Alternatives: SSE (EventSource), long polling, or libraries like Socket.io / Ably.

Q72
What is the React DevTools Profiler API?
Advanced

The <Profiler> component lets you programmatically measure rendering performance in production builds.

import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration, baseDuration) {
  sendToAnalytics({ id, phase, actualDuration, baseDuration });
}

<Profiler id="Navigation" onRender={onRenderCallback}>
  <Navigation />
</Profiler>

Parameters: id (label), phase (mount/update), actualDuration (time for this render), baseDuration (estimated without memo), startTime, commitTime.

Q73
How do you create a fully accessible modal dialog in React?
Advanced
function Modal({ isOpen, onClose, title, children }) {
  const dialogRef = useRef();

  useEffect(() => {
    if (!isOpen) return;
    dialogRef.current?.focus();
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return createPortal(
    <div role="dialog" aria-modal="true" aria-labelledby="modal-title">
      <div ref={dialogRef} tabIndex={-1}>
        <h2 id="modal-title">{title}</h2>
        {children}
        <button onClick={onClose} aria-label="Close">✕</button>
      </div>
    </div>,
    document.body
  );
}
Q74
What is state normalization and why is it important?
Advanced

State normalization stores entity data in a flat map (keyed by ID) rather than nested arrays. This avoids data duplication and makes updates O(1) instead of O(n).

// ❌ Denormalized — hard to update a specific post
{ posts: [{ id: 1, author: { id: 5, name: 'Alice' } }, ...] }

// ✅ Normalized — each entity stored once
{
  posts: { ids: [1], entities: { 1: { id: 1, authorId: 5 } } },
  users: { ids: [5], entities: { 5: { id: 5, name: 'Alice' } } }
}

Redux Toolkit’s createEntityAdapter automates this pattern. TanStack Query handles it automatically via query cache keying.

Q75
How do you measure and improve Core Web Vitals in a React app?
Advanced

Key CWV metrics and React-specific fixes:

  • LCP (Largest Contentful Paint) — SSR or SSG, preload hero image, eliminate render-blocking resources.
  • INP (Interaction to Next Paint) — debounce handlers, use startTransition, avoid long tasks, virtualize large lists.
  • CLS (Cumulative Layout Shift) — set explicit dimensions on images/iframes, avoid injecting content above existing content.
// Measure with web-vitals library
import { onINP, onLCP, onCLS } from 'web-vitals';
onINP(metric => sendToAnalytics(metric));
onLCP(metric => sendToAnalytics(metric));
Q76
What is the use Hook (React 19)?
Advanced

The use Hook (React 19) lets you read the value of a resource — a Promise or a Context — inside render. Unlike other hooks, use can be called inside conditionals and loops.

// Reading a Context with use (equivalent to useContext)
import { use } from 'react';

function Heading({ children }) {
  const level = use(LevelContext);
  return <{`h${level}`}>{children}</{`h${level}`}>;
}

// Reading a Promise (must be wrapped / cache)
function Comments({ commentsPromise }) {
  const comments = use(commentsPromise); // suspends until resolved
  return comments.map(c => <Comment key={c.id} comment={c} />);
}
Q77
How do you architect a large-scale React application?
Advanced

Key principles for large-scale React apps:

  • Feature-based folder structure — group by domain (features/auth, features/dashboard) not by type.
  • Clear layer separation — UI components → hooks/services → API layer.
  • Strict module boundaries — use barrel exports and enforce with ESLint import rules.
  • Micro-frontend or monorepo — NX/Turborepo for team scalability.
  • Design system — shared component library (Storybook) consumed by all features.
  • Typed contracts — TypeScript + Zod for runtime validation of API responses.
  • Testing pyramid — unit (hooks/utils), integration (RTL), e2e (Playwright).
Q78
What are Server Actions in Next.js / React 19?
Advanced

Server Actions let you call server-side functions directly from client components — without manually writing API routes. They’re marked with 'use server' and can be called from form actions or event handlers.

// actions.ts — runs on server
'use server';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  await db.post.create({ data: { title } });
  revalidatePath('/posts');
}

// Client component
<form action={createPost}>
  <input name="title" />
  <button type="submit">Create</button>
</form>
Q79
What is Streaming SSR and how does it work in React 18?
Advanced

Streaming SSR uses renderToPipeableStream (Node.js) or renderToReadableStream (Edge) to stream HTML to the browser in chunks rather than waiting for the full page to render.

import { renderToPipeableStream } from 'react-dom/server';

res.setHeader('Content-Type', 'text/html');
const { pipe } = renderToPipeableStream(<App />, {
  onShellReady() { pipe(res); }, // send shell immediately
  onError(err) { console.error(err); }
});

Wrapped in Suspense, slow components don’t block the initial shell. The client progressively hydrates chunks as they stream in. Result: faster FCP + TTFB without sacrificing dynamic content.

Q80
How do you implement a micro-frontend with React?
Advanced

Micro-frontends decompose a large frontend into independently deployable apps owned by different teams. Common approaches with React:

  • Module Federation (Webpack 5 / Rspack) — dynamically load remote components at runtime.
  • iframes — strong isolation, simple, but limited UX/communication.
  • Custom Elements / Web Components — wrap React apps as standards-based elements.
  • Single-SPA — orchestrates multiple framework apps on one page.
// webpack.config.js (Host) — Module Federation
new ModuleFederationPlugin({
  remotes: {
    cart: 'cart@https://cart.example.com/remoteEntry.js',
  },
})

// In host app
const CartWidget = lazy(() => import('cart/CartWidget'));

Expert Level

Q81
Explain React’s scheduling and priority system in detail.
Expert

React uses a scheduler (the scheduler package) that assigns lanes and priorities to work. Work is queued in a min-heap and processed in priority order using cooperative scheduling (yielding to the browser event loop).

Priority levels (React 18 lanes):

  • SyncLaneflushSync, legacy mode. Always processes before paint.
  • InputContinuousLane — pointer/scroll events. Processed before next frame.
  • DefaultLane — normal setState. Batch and process asap.
  • TransitionLanestartTransition. Can be interrupted by higher-priority work.
  • OffscreenLane — pre-rendering hidden content.

The scheduler uses MessageChannel to schedule work asynchronously, yielding every ~5ms to let the browser handle input/paint.

Q82
How does React implement batching internally?
Expert

React wraps event handlers in batchedUpdates. In React 17 and earlier, this only applied inside React event handlers. In React 18, batching is automatic everywhere via a mechanism called automatic batching.

Internally: each setState call enqueues an update on the fiber’s updateQueue. React defers the re-render by scheduling work asynchronously. Only after the current execution context ends does React flush the queue and process all enqueued updates together in a single render pass.

Calling flushSync forces immediate synchronous flush of the queue.

Q83
What is the React Compiler (React Forget)?
Expert

The React Compiler (previously codenamed “React Forget”) is a Babel plugin developed by the React team that automatically memoizes components, hooks, and JSX expressions at compile time — eliminating the need for manual useMemo, useCallback, and React.memo.

It uses static analysis to identify values whose referential identity needs to be preserved, then inserts the correct memoization. It understands React’s rules of hooks and can prove safety of optimizations.

Released as part of React 19 with Meta running it in production on Instagram.com before public release. Opt in via babel config or Next.js config option.

Q84
How do you build a custom React renderer?
Expert

React’s reconciler (react-reconciler) is decoupled from the host environment. You implement a “host config” that defines how to create, update, and delete nodes in your custom target.

import Reconciler from 'react-reconciler';

const HostConfig = {
  createInstance(type, props) { return { type, props, children: [] }; },
  appendChildToContainer(container, child) { container.children.push(child); },
  commitUpdate(instance, _, __, ___, newProps) { instance.props = newProps; },
  removeChildFromContainer(container, child) { /* ... */ },
  supportsMutation: true,
  // ... ~30 other required methods
};

const MyRenderer = Reconciler.createContainer(HostConfig);

export function render(element, container) {
  MyRenderer.updateContainer(element, container);
}

Examples in the wild: React Three Fiber (WebGL/Three.js), React PDF, React Native, Ink (terminal).

Q85
How does React’s Context API work internally?
Expert

Internally, a Context object has a $$typeof symbol and stores the current value on the provider’s fiber during reconciliation.

When a Provider renders, React pushes the new value onto a context stack (a linked list of fiber nodes). When a useContext consumer renders, React walks up the fiber tree to find the nearest matching Provider and reads its current value.

When the Provider’s value changes, React propagates the change by marking all consumers as needing re-render (a “context propagation bailout” scan). This is O(n) in the subtree size, which is why splitting contexts and memoizing consumers matters for performance.

Q86
What are the gotchas of using React.memo with objects and functions?
Expert

React.memo uses shallow reference equality. Every render creates new object/array/function references, so memoization is defeated without useMemo/useCallback.

const Child = React.memo(({ style, onClick }) => <div style={style} onClick={onClick}>...</div>);

// ❌ New object on every Parent render — memo is useless
<Child style={{ color: 'red' }} onClick={() => doThing()} />

// ✅ Stable references
const style    = useMemo(() => ({ color: 'red' }), []);
const onClick  = useCallback(() => doThing(), []);
<Child style={style} onClick={onClick} />

Alternative: use the React Compiler which handles this automatically, or design components to accept primitive props.

Q87
How do you handle race conditions in data fetching with React?
Expert

Race conditions occur when a user triggers multiple requests and the last one resolves before an earlier one, displaying stale data. Solutions:

// Pattern 1: cleanup flag
useEffect(() => {
  let active = true;
  fetchData(id).then(data => { if (active) setData(data); });
  return () => { active = false; };
}, [id]);

// Pattern 2: AbortController
useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal })
    .then(r => r.json())
    .then(setData)
    .catch(e => { if (e.name !== 'AbortError') setError(e); });
  return () => controller.abort();
}, [url]);

TanStack Query handles race conditions automatically — it cancels in-flight queries when a newer request supersedes them.

Q88
How would you implement a feature-flag system in React?
Expert
// flags.ts
export const flags = {
  newDashboard: Boolean(process.env.NEXT_PUBLIC_FLAG_NEW_DASHBOARD),
};

// Hook
function useFlag(key) {
  const { user } = useAuth();
  const remoteFlags = useQuery({ queryKey: ['flags', user.id], queryFn: fetchFlags });
  return remoteFlags.data?.[key] ?? flags[key] ?? false;
}

// Usage
function App() {
  const showNewDash = useFlag('newDashboard');
  return showNewDash ? <NewDashboard /> : <OldDashboard />;
}

Production systems use services like LaunchDarkly, Statsig, or GrowthBook, which add targeting rules, A/B experimentation, kill switches, and analytics.

Q89
Implement a pub/sub event bus as a React hook.
Expert
// eventBus.ts
type Handler = (data: unknown) => void;
const listeners = new Map<string, Set<Handler>>();

export const eventBus = {
  on(event: string, handler: Handler) {
    if (!listeners.has(event)) listeners.set(event, new Set());
    listeners.get(event)!.add(handler);
    return () => listeners.get(event)!.delete(handler);
  },
  emit(event: string, data?: unknown) {
    listeners.get(event)?.forEach(h => h(data));
  }
};

// Hook
function useEvent<T>(event: string, handler: (data: T) => void) {
  const handlerRef = useRef(handler);
  handlerRef.current = handler;

  useEffect(() => {
    return eventBus.on(event, (data) => handlerRef.current(data as T));
  }, [event]);
}
Q90
How do you implement undo/redo in React state?
Expert
function useUndoRedo<T>(initial: T) {
  const [history, setHistory] = useState<T[]>([initial]);
  const [index, setIndex]     = useState(0);

  const current = history[index];

  const set = useCallback((newState: T) => {
    const next = history.slice(0, index + 1); // drop future states
    setHistory([...next, newState]);
    setIndex(next.length);
  }, [history, index]);

  const undo = () => setIndex(i => Math.max(0, i - 1));
  const redo = () => setIndex(i => Math.min(history.length - 1, i + 1));

  return { current, set, undo, redo,
    canUndo: index > 0,
    canRedo: index < history.length - 1
  };
}
Q91
How would you implement a multi-step wizard with URL-synced state?
Expert
const STEPS = ['info', 'payment', 'confirm'] as const;

function Wizard() {
  const [searchParams, setSearchParams] = useSearchParams();
  const stepParam = searchParams.get('step');
  const stepIndex = STEPS.indexOf((stepParam ?? 'info') as typeof STEPS[0]);
  const currentStep = STEPS[Math.max(0, stepIndex)];

  const [formData, setFormData] = useState({});

  function goTo(step: typeof STEPS[0]) {
    setSearchParams({ step });
  }

  function saveAndNext(data: object) {
    setFormData(prev => ({ ...prev, ...data }));
    const nextStep = STEPS[stepIndex + 1];
    if (nextStep) goTo(nextStep);
  }

  return (
    <>
      {currentStep === 'info'    && <InfoStep    onNext={saveAndNext} />}
      {currentStep === 'payment' && <PaymentStep onNext={saveAndNext} />}
      {currentStep === 'confirm' && <ConfirmStep data={formData}    />}
    </>
  );
}
Q92
How does React integrate with Web Workers?
Expert

Web Workers run JS in a background thread, off the main thread. React UI lives on the main thread, but you can offload CPU-heavy work (image processing, search indexing, AI inference) to a worker and communicate via postMessage.

// worker.ts
self.onmessage = ({ data }) => {
  const result = heavyComputation(data);
  self.postMessage(result);
};

// useWorker.ts
function useWorker(workerPath: string) {
  const workerRef = useRef<Worker>();
  useEffect(() => {
    workerRef.current = new Worker(workerPath, { type: 'module' });
    return () => workerRef.current?.terminate();
  }, [workerPath]);

  const compute = (data: unknown) => new Promise(resolve => {
    workerRef.current!.onmessage = ({ data }) => resolve(data);
    workerRef.current!.postMessage(data);
  });

  return { compute };
}
Q93
What is React’s act() testing utility and why is it important?
Expert

act() ensures that all state updates, effects, and re-renders are flushed before you make assertions in tests. Without it, tests may assert on stale DOM state.

import { act } from 'react';
import { render } from '@testing-library/react';

test('loads and displays data', async () => {
  await act(async () => {
    render(<DataLoader />);
  });
  // Now state updates + effects have all run
  expect(screen.getByText('Data loaded')).toBeInTheDocument();
});

React Testing Library wraps all its utilities (render, userEvent, fireEvent) in act automatically, which is why you typically don’t need to call it directly.

Q94
How do you implement a design system with React and CSS-in-JS?
Expert

A production design system typically combines:

  • Design tokens — CSS custom properties or JS objects for color, spacing, typography.
  • Primitive components — Box, Text, Stack, Grid with token-based props.
  • Compound components — Card, Modal, DataTable built from primitives.
  • Storybook — living documentation with interactive playground.
// tokens.ts
export const tokens = {
  colors: { primary: '#0066cc', danger: '#dc2626' },
  space: [0, 4, 8, 16, 24, 32, 48, 64],
};

// Button with variant system (using vanilla-extract or Tailwind CVA)
const button = cva('rounded font-medium', {
  variants: {
    intent: {
      primary: 'bg-blue-600 text-white',
      danger:  'bg-red-600 text-white',
      ghost:   'bg-transparent border',
    },
    size: { sm: 'px-2 py-1 text-sm', lg: 'px-6 py-3 text-lg' },
  },
  defaultVariants: { intent: 'primary', size: 'sm' },
});
Q95
How would you implement a collaborative real-time editor (like Google Docs) in React?
Expert

Key engineering challenges and solutions:

  • Conflict resolution — use Operational Transformation (OT) or CRDTs (Yjs, Automerge) to merge concurrent edits without conflicts.
  • Sync — WebSocket for real-time updates; CRDT diffs are small and efficient.
  • Awareness — broadcast cursor positions and user presence.
  • Offline support — CRDTs can merge divergent offline edits on reconnect.
  • React integration — bind Yjs doc changes to React state via y-react or custom useSyncExternalStore hook.
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://y.example.com', 'room-1', ydoc);
const yText = ydoc.getText('content');

// In component:
useSyncExternalStore(
  cb => { yText.observe(cb); return () => yText.unobserve(cb); },
  () => yText.toString()
);
Q96
What is useSyncExternalStore and when do you need it?
Expert

useSyncExternalStore (React 18) is the correct way to subscribe to external (non-React) stores inside components. It ensures consistency under concurrent rendering by providing a snapshot mechanism.

import { useSyncExternalStore } from 'react';

function useWindowWidth() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => window.innerWidth,      // getSnapshot (client)
    () => 1024                   // getServerSnapshot (SSR)
  );
}

Use it when integrating with external state stores (Redux, Zustand, RxJS, browser APIs) to avoid tearing — inconsistent state during concurrent renders.

Q97
How does React’s Offscreen API (Activity) work?
Expert

The Offscreen component (API still stabilizing, called Activity in latest React canary) lets React pre-render trees that are not yet visible, or cache them when they’re hidden — without destroying their state.

<Offscreen mode="hidden">
  <ExpensiveTab />  // rendered but not visible, state preserved
</Offscreen>

Mode options:

  • visible — normal rendering
  • hidden — rendered off-screen, state preserved, effects paused
  • manual — developer controls visibility transitions

Enables: instant tab switching (pre-rendered), keepalive patterns, background rendering. Replaces the display: none hack that destroys React state.

Q98
Explain React’s tearing problem in concurrent mode and how it’s solved.
Expert

Tearing occurs when React renders a UI snapshot but an external store updates mid-render, causing different components to see different values of the same state — a visually inconsistent UI.

Scenario: Component A reads store version 1 → store updates to version 2 → Component B reads version 2 → they disagree on the same value.

React’s solution: useSyncExternalStore uses a two-phase “getSnapshot” check. After rendering, React verifies that all snapshots are still consistent. If not, it synchronously re-renders — trading some concurrency for consistency.

Libraries using legacy subscription patterns (e.g. old Redux useSelector) are vulnerable to tearing in concurrent mode until they migrate to useSyncExternalStore. RTK Query and modern Zustand handle this correctly.

Q99
How would you build a high-performance data grid with 100,000 rows in React?
Expert

A production-grade data grid for 100k rows requires multiple techniques in combination:

  • Row virtualization — @tanstack/react-virtual or react-window. Only render ~20-50 visible rows.
  • Column virtualization — virtual horizontal scrolling for wide tables.
  • Memoized row componentsReact.memo per row with stable props.
  • Immutable data structures — structural sharing for efficient diffing.
  • Lazy loading — paginated server requests or cursor-based fetching.
  • Web Worker offload — filtering, sorting, and aggregation off the main thread.
  • Canvas rendering — for extreme performance (AG Grid’s column virtualizer uses canvas for headers).

Production libraries: AG Grid, TanStack Table (headless), react-data-grid.

Q100
What does the future of React look like? (React 19 and beyond)
Expert

React 19 ships several transformative features that change how React apps are built:

  • React Compiler — automatic memoization; useMemo/useCallback/React.memo become largely unnecessary.
  • Server Components (stable) — zero-JS server-rendered components, direct data access, smaller bundles.
  • Server Actions (stable) — async server functions callable from clients, simplifying API route boilerplate.
  • use() Hook — read Promises and Contexts in render, even inside conditionals.
  • useOptimistic — first-class API for optimistic UI updates.
  • useFormStatus / useFormState — form state management tied to server actions.
  • Asset loading APIspreload, prefetchDNS, preinit for resource hints directly from components.
  • Activity (Offscreen) — keep-alive hidden subtrees with paused effects.

The direction: less client JS, better DX, compiler-driven optimization, and deeper server/client boundary awareness baked into the framework.

React JS Interview Questions and Answers: Expert Level

100 Expert-Level React Questions. Conquer React JS machine rounds and system design interviews by mastering React Architecture, Fiber Internals, RSCs, Security, and Custom Renderers.

1. React Architecture, Fiber & Diffing

Q1
Explain the React Fiber architecture. How does it shift React from a “push” to a “pull” based execution model?

Before React 16, the “Stack Reconciler” used synchronous, recursive tree traversal. Once an update started, it could not be interrupted, leading to dropped frames if rendering took longer than 16ms. Fiber is a complete rewrite representing a cooperative scheduling engine. It breaks rendering work into a linked list of mutable “Fiber Nodes.” This allows React to pause rendering, yield control back to the browser to handle user input, and resume or discard the work based on assigned Lane priorities.

Real-World Scenario: Imagine a quick-commerce dashboard managing dark store operations within a strict 30-minute delivery frame. The UI is flooded with real-time WebSocket updates for inventory. Simultaneously, the store manager is typing into a search bar. Fiber allows us to tag the inventory updates as low priority, ensuring the search input (high priority) remains completely snappy and unbroken.
import { useState, useTransition, useEffect } from 'react';

export function InventoryDash({ socket }) {
  const [searchTerm, setSearchTerm] = useState('');
  const [inventory, setInventory] = useState([]);
  const [isPending, startTransition] = useTransition();

  // High priority UI update
  const handleSearch = (e) => setSearchTerm(e.target.value);

  useEffect(() => {
    socket.on('stock_update', (data) => {
      // Yielding to Fiber: Process stock updates in the background
      startTransition(() => {
        setInventory(data);
      });
    });
    return () => socket.off('stock_update');
  }, [socket]);

  return (
    <div>
      <input onChange={handleSearch} value={searchTerm} placeholder="Search SKUs..." />
      <List data={inventory} stale={isPending} />
    </div>
  );
}
Q2
How does React’s Diffing Algorithm achieve O(n) complexity instead of the standard O(n³) tree edit distance?

A generalized algorithm to find the minimum number of operations to transform one tree into another has a complexity of O(n³). For a UI with 1000 nodes, this means one billion comparisons. React implements a heuristic O(n) algorithm based on two assumptions:

  1. Two elements of different types will produce different trees. React will tear down the old tree completely and build the new one from scratch.
  2. Developers can hint at which child elements remain stable across renders using the key prop.
Real-World Scenario: If you render a list of 50 localized delivery zones and a user deletes the first zone, using the array index as the `key` forces React to re-render and mutate all 49 remaining items because the indexes shifted. Using a unique database ID as the `key` allows React’s diffing engine to realize the elements simply shifted position, executing a cheap DOM move operation instead of 49 re-renders.
// BAD: Array index keys break the O(n) diffing heuristic on deletion/sorting
{zones.map((zone, index) => (
  <DeliveryZone key={index} data={zone} /> 
))}

// GOOD: Stable identities enable efficient Fiber reconciliation
{zones.map((zone) => (
  <DeliveryZone key={zone.zoneId} data={zone} />
))}
Q3
Explain the “workInProgress” tree and the concept of Double Buffering in React.

Double buffering is a technique borrowed from game development to prevent screen tearing. React maintains two Fiber trees: the current tree (reflecting the exact state of the visible DOM) and the workInProgress tree (the draft being calculated in memory).

During the Render phase, all calculations and diffing happen on the workInProgress tree without touching the DOM. Once the Commit phase completes the DOM mutations, React simply swaps the pointers: the workInProgress tree instantly becomes the current tree. If an error or high-priority interruption occurs mid-render, React can safely throw away the workInProgress tree without leaving the user with a broken, half-rendered UI.

Q4
What are Fiber “Lanes” and how do they replace the old Expiration Times model?

Originally, React assigned “expiration times” to updates to determine priority. However, time is linear, making it difficult to express complex concepts like “batch these two background tasks together but preempt them for this specific input.”

React 17+ introduced “Lanes”—a 32-bit bitmask system. Each bit represents a priority level (e.g., SyncLane for inputs, TransitionLane for data fetching). Bitmasks allow the Scheduler to use highly efficient bitwise operations (like & and |) to merge updates, check for overlapping priorities, and decide what task to execute next instantaneously.

Q5
Describe the “Bailout” mechanism. How does Context propagation bypass it?

When a component’s state or props remain unchanged (verified via shallow equality in React.memo), Fiber “bails out.” It aborts traversing that branch and clones the node directly from the current tree to the workInProgress tree, saving massive CPU cycles.

However, if a Context Provider updates, React scans down the entire tree to find components hooked to that Context. It marks those specific Fiber nodes with a forced update Lane. Even if intermediate parent components trigger a bailout, React will strictly bypass the bailout for the marked Context consumers to ensure they receive the fresh data.

Q6
What is the technical distinction between the Render Phase and the Commit Phase?

The Render Phase is pure, asynchronous, and interruptible. React calls your component functions, calculates the changes, and flags Fiber nodes with “Effect Tags” (e.g., Placement, Update, Deletion). No DOM mutations happen here.

The Commit Phase is synchronous and uninterruptible. React iterates over the list of Effect Tags and executes the physical DOM mutations (appendChild, removeChild). Afterward, it fires lifecycle hooks like componentDidMount and useLayoutEffect.

Q7
When would an Architect explicitly choose useLayoutEffect over useEffect?

useEffect fires asynchronously after the browser has painted the screen. useLayoutEffect fires synchronously before the browser paints. If you mutate the DOM in useEffect, the user will see a visual flicker (the first paint, then the mutation).

Real-World Scenario: If you are rendering an intricate astrology chart and need to position a tooltip dynamically based on the bounding box of a specific star node, using useEffect causes the tooltip to render at 0,0 and jump to the star. useLayoutEffect allows you to measure the node and apply the precise coordinates before the browser ever paints, preventing the layout shift.
import { useLayoutEffect, useRef, useState } from 'react';

export function StarTooltip({ targetRef }) {
  const tooltipRef = useRef(null);
  const [coords, setCoords] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    // Measures and sets coordinates synchronously before paint
    const targetRect = targetRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();
    
    setCoords({
      top: targetRect.top - tooltipRect.height,
      left: targetRect.left + (targetRect.width / 2) - (tooltipRect.width / 2)
    });
  }, [targetRef]);

  return <div ref={tooltipRef} style={{ ...coords, position: 'absolute' }}>Chart Data</div>;
}
Q8
How does React’s Synthetic Event System optimize memory usage via Event Delegation?

Browsers consume heavy memory if you attach individual event listeners (like onClick) to thousands of DOM nodes. React solves this by ignoring your inline onClick handlers completely at the DOM level.

Instead, React attaches exactly one event listener per event type to the root container (e.g., div#root). When a user clicks a button, the browser event bubbles to the root. React intercepts it, wraps it in a cross-browser compatible “Synthetic Event,” determines which Fiber node was the target, and simulates the capturing and bubbling phases entirely in memory.

Q9
How does Fiber traversal handle Error Boundaries during a catastrophic render failure?

If an error is thrown during the Render phase, React catches it and halts the workInProgress traversal. It then walks up the Fiber tree looking for the nearest node tagged as an Error Boundary (a class component implementing static getDerivedStateFromError).

Real-World Scenario: If an AI prediction widget built on LangChain crashes due to a malformed JSON response or a sudden mobile network interruption, you don’t want the entire app to blank out (White Screen of Death). The Error Boundary isolates the crash to that specific subtree, allowing the rest of the application to remain functional while rendering a fallback UI for the broken widget.
class WidgetErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    // Halts the crash and schedules a fallback render
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Send to Datadog/Sentry
    logToMonitoringService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <div className="fallback">AI Prediction temporarily unavailable.</div>;
    }
    return this.props.children;
  }
}
Q10
Why does React strictly utilize Shallow Equality instead of Deep Equality during reconciliation?

Deep equality requires recursive traversal of nested objects. If a state object has hundreds of keys, executing a deep comparison on every single keystroke or data tick would O(N) throttle the CPU, destroying performance.

Shallow equality (oldProp === newProp) checks memory references, which is an O(1) instantaneous operation. This is why immutability is an architectural requirement in React. By returning a brand new object reference when mutating state, React instantly knows the data changed without having to inspect the internal keys.

2. Advanced Concurrency & React 19

Q11
How does React 19’s use() API fundamentally differ from await in an async function?

Standard await halts the execution of a JavaScript function entirely until a Promise resolves. In React, halting a render function blocks the thread. use() is designed to integrate with React’s Fiber engine and Suspense. When use() encounters an unresolved Promise, it actually throws that Promise up the component tree. React catches it, suspends the component, renders the nearest Suspense fallback, and resumes rendering only when the Promise resolves.

Crucially, because use() hooks into the compiler rather than standard Hook dispatchers, it can be called conditionally inside if statements and loops, breaking the traditional Rules of Hooks.

Real-World Scenario: You have an application where premium users see an AI-generated summary, but free users do not. With standard hooks, you’d have to unconditionally fetch the data or create complex wrapper components. With use(), you can conditionally suspend only if the user is premium.
import { use, Suspense } from 'react';

function AISummary({ isPremium, summaryPromise }) {
  // Valid in React 19! We can call `use()` inside a conditional block.
  if (!isPremium) {
    return <div>Upgrade to view AI Summary</div>;
  }

  // Throws to Suspense boundary if unresolved, returns data if resolved
  const summary = use(summaryPromise);
  
  return <p>{summary}</p>;
}
Q12
What is “Tearing” in UI rendering, and how does Concurrent React mitigate it?

“Tearing” occurs when a React application’s UI becomes inconsistent because an external data source (like a Redux store) mutates while React is in the middle of a concurrent Render phase. Because concurrent rendering yields to the main thread, an external event could change the store state. Half the UI might render with the old state, and the bottom half with the new state.

React mitigates this using the useSyncExternalStore hook. It forces React to track the external store’s version during the render. If a mutation is detected mid-render, React immediately discards the torn workInProgress tree and triggers a synchronous, high-priority re-render to guarantee UI consistency.

Q13
Explain the architectural mechanics of React 19 Server Actions.

Server Actions abstract away the API layer. When you mark a function with 'use server', the React compiler extracts it from the client bundle and generates a secure, hidden RPC (Remote Procedure Call) endpoint. When a client component invokes the action, React automatically serializes the arguments, sends a POST request to that endpoint, executes the Node.js logic (like database mutations), and streams the updated UI back to the client.

Real-World Scenario: In an e-commerce checkout flow, handling form submissions traditionally requires maintaining `useState` for loading, error, and success states, plus a `fetch` call to an API route. Server Actions allow you to bind a backend mutation directly to a form, automatically handling CSRF protection and progressively enhancing the form (it works even if JS hasn’t fully loaded).
// action.ts
'use server'
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';

export async function updateCartQuantity(formData: FormData) {
  const itemId = formData.get('itemId');
  const qty = parseInt(formData.get('quantity'), 10);
  
  await db.cart.update({ itemId, qty });
  
  // Instructs the server to stream the updated RSC payload to the client
  revalidatePath('/cart'); 
}

// CartItem.tsx (Client or Server Component)
import { updateCartQuantity } from './action';

export function CartItem({ item }) {
  return (
    <form action={updateCartQuantity}>
      <input type="hidden" name="itemId" value={item.id} />
      <input type="number" name="quantity" defaultValue={item.qty} />
      <button type="submit">Update</button>
    </form>
  );
}
Q14
What is the taint API in React 19 and what architectural security problem does it solve?

With Server Components, the boundary between backend and frontend is porous. It becomes incredibly easy to accidentally pass a raw database object as a prop to a Client Component, which serializes sensitive data (like password hashes or internal API keys) directly into the browser’s HTML payload.

React 19 introduces taintObjectReference. If an architect explicitly “taints” a user object at the database layer, React’s serialization engine will monitor it. If a developer ever accidentally passes that tainted object to a Client Component, the React compiler will aggressively throw a fatal error, preventing the data leak.

Q15
How does React’s unified async transition model improve form submissions?

Previously, form submissions required boilerplate: e.preventDefault(), setting isSubmitting to true, awaiting a fetch, and handling errors. React 19 native forms integrate directly with the transition API via useActionState (formerly useFormState) and useFormStatus.

Real-World Scenario: You are building an enterprise file upload portal. During a heavy upload, you want to disable the submit button and show a spinner. Instead of drilling `isSubmitting` state down through layers of components, a deeply nested Submit button can use `useFormStatus` to automatically read the pending state of its parent form, drastically reducing prop-drilling.
import { useFormStatus } from 'react-dom';

// This component can be deeply nested inside the <form>
function SubmitButton() {
  // Automatically reads the pending state of the nearest parent form action
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Uploading Securely...' : 'Submit Document'}
    </button>
  );
}
Q16
Explain the architectural difference between useTransition and useDeferredValue.

Both APIs tell React to deprioritize a render, but they operate on different levels of abstraction.

useTransition is Imperative: You wrap the state setter function (the action). You explicitly tell React, “The state update caused by this specific click/typing event is low priority.”

useDeferredValue is Declarative: You wrap the data value itself. You tell React, “I don’t care where this value came from (props, context, external store), if it changes, keep showing the old value for high-priority renders, and schedule a background render to figure out the new UI.”

Q17
How does React 19 handle stylesheet and script precedence during concurrent renders?

Historically, injecting CSS files asynchronously caused a Flash of Unstyled Content (FOUC), and React didn’t know when a stylesheet was fully loaded. React 19 introduces native support for Document Metadata. By rendering <link rel="stylesheet" precedence="default"> inside any component, React hoists the link to the <head>.

Crucially, React will now suspend the Commit phase. It holds the DOM update in memory until the browser confirms the stylesheet is downloaded and parsed, guaranteeing the user never sees unstyled markup.

Q18
What are the implications of the React Compiler (React Forget) on the dependency array mental model?

React requires developers to manually declare dependency arrays in useEffect, useMemo, and useCallback to prevent infinite loops and stale closures. The React Compiler is a build-time Babel plugin that performs static code analysis on your component’s Abstract Syntax Tree (AST).

It understands the data flow and automatically injects low-level memoization caches (using arrays) around values that don’t mutate. Architecturally, this means you can delete almost all useMemo and useCallback hooks from your codebase. The compiler guarantees that a component or calculation will only re-execute if its structural, semantic dependencies genuinely change.

Q19
What is “Selective Hydration” and how does Suspense enable it?

Standard Server-Side Rendering (SSR) requires the browser to download the entire JavaScript bundle before the page becomes interactive (the “Uncanny Valley”). Selective Hydration solves this using <Suspense> boundaries.

React sends the HTML immediately. Instead of waiting for all JS, it hydrates the DOM in chunks. If a user clicks or interacts with a specific Suspense boundary before it is hydrated, React’s event delegation system catches the click, bumps that specific chunk to the highest priority Lane, hydrates it instantly, and replays the user’s click event so no interaction is lost.

Q20
How does React 19’s useOptimistic hook handle race conditions automatically?

Optimistic UI (updating the screen before the server confirms success) usually requires complex rollback logic if the network fails. useOptimistic ties the optimistic state directly to the lifecycle of an ongoing async Action.

Real-World Scenario: A user clicks a “Like” button on a post. The network is slow. useOptimistic instantly increments the like counter on the screen. If the Server Action resolves successfully, the optimistic state is quietly swapped for the true server state. If the Server Action throws an error, the Action terminates, and React automatically drops the optimistic state, snapping the UI back to the original value without you writing a single line of manual rollback code.
import { useOptimistic } from 'react';
import { likePostAction } from './actions';

export function LikeButton({ post }) {
  // optimisticLikes lives only as long as the action is pending
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    post.likeCount,
    (state, amount) => state + amount
  );

  return (
    <form action={async () => {
      addOptimisticLike(1); // Instantly update UI
      await likePostAction(post.id); // True mutation
    }}>
      <button type="submit">Like ({optimisticLikes})</button>
    </form>
  );
}

3. Server Components (RSC) & Next.js App Router

Q21
What is the fundamental difference in the wire format (RSC payload) between Server Components and SSR HTML?

Standard SSR sends a finalized, static HTML string to the browser. While great for First Contentful Paint (FCP), it’s a dead end—if you navigate, the server must send a brand new HTML document, destroying client-side state.

The RSC payload is a highly specialized JSON-like stream. It represents the component tree’s Virtual DOM structure, the serialized props fetched from the database, and strict module references (import paths) indicating exactly where Client Component JS chunks should be inserted. React reads this payload on the client and merges it into the existing DOM without destroying client state (like a playing video or an active text input).

Real-World Scenario: Imagine a master-detail dashboard. The sidebar is a Client Component with an active search filter. When the user clicks a specific item, an RSC payload is fetched to render the details pane. The RSC payload patches the DOM seamlessly; the HTML string format of SSR would force a full page reload, erasing the user’s sidebar search filter.
Q22
How do React Server Components interact with Client Components? Can a Client import a Server Component?

Server Components can freely import and render Client Components. However, Client Components cannot import Server Components. If a component with 'use client' imports a Server Component, that Server Component immediately becomes a Client Component, bloats the JS bundle, and causes errors if it uses Node.js modules like `fs`.

To bypass this restriction architecturally, we use the Composition Pattern. The Server Component imports both the Client Component and another Server Component, passing the latter into the former as a children prop.

// Layout.tsx (Server Component)
import { ClientSidebar } from './ClientSidebar';
import { ServerDataFeed } from './ServerDataFeed';

export default function Layout() {
  return (
    // We pass the Server Component through the Client Component's "hole"
    <ClientSidebar>
      <ServerDataFeed /> 
    </ClientSidebar>
  );
}
Q23
What is “poisoning” in the context of React Server Components?

Because Next.js blends server and client code in the same directory structure, it is incredibly easy to accidentally import a utility function containing database credentials or Node-specific libraries (like `crypto`) into a Client Component. This leaks secrets to the browser and crashes the client bundle.

Architects use “poisoning” to prevent this. By importing the server-only package at the top of sensitive files, you explicitly poison them against client usage. If a developer mistakenly imports that file into a component with a 'use client' directive, the Webpack/Turbopack build instantly fails, preventing catastrophic security breaches.

// lib/db.ts
import 'server-only'; // POISON: Will break the build if leaked to client
import { Pool } from 'pg';

export const db = new Pool({
  connectionString: process.env.DATABASE_URL,
});
Q24
What are the strict constraints on props passed from a Server Component to a Client Component?

Props crossing the network boundary from Server to Client must be strictly serializable. Under the hood, React converts these props into strings inside the RSC payload.

You can pass primitives (strings, numbers, booleans), arrays, Maps, Sets, Dates, and plain objects. You cannot pass class instances (like a custom new User() object), un-serializable APIs (like DOM nodes), or standard functions (event handlers).

Real-World Exception: The only time you can pass a function across the boundary is if that function is marked with 'use server' (a Server Action). React intercepts this, serializes it as a hidden API endpoint reference, and passes that reference to the Client Component.
Q25
Explain how Next.js handles Partial Prerendering (PPR) leveraging React’s Suspense.

Partial Prerendering (PPR) is a groundbreaking optimization that merges Static Site Generation (SSG) and Server-Side Rendering (SSR). Historically, a page was either fast (static) or personalized (dynamic, but slow TTFB).

PPR allows Next.js to statically generate the outer shell of your layout at build time. The dynamic parts are wrapped in <Suspense>. Upon request, the server instantly sends the static shell from a CDN edge node (TTFB near zero). Simultaneously, the server executes the async database calls for the dynamic parts and streams them into the Suspense boundaries over the same HTTP connection.

import { Suspense } from 'react';
import { Skeleton } from '@/components/ui/Skeleton';
import AIPrediction from '@/components/AIPrediction';

export default function HoroscopePage({ params }) {
  return (
    <div className="layout">
      <!-- STATIC SHELL: Cached and served instantly -->
      <h1>Daily Horoscope: {params.sign}</h1>
      <nav>Profile | Settings</nav>
      
      <!-- DYNAMIC STREAM: Resolves and patches the DOM seconds later -->
      <Suspense fallback={<Skeleton message="Consulting the stars..." />}>
        <AIPrediction sign={params.sign} />
      </Suspense>
    </div>
  );
}
Q26
How does the Next.js App Router Client-Side Cache differ from the Full Route Cache?

The Full Route Cache lives entirely on the server. At build time (or during revalidation), Next.js renders the HTML and RSC payload and stores it on the disk/CDN. Multiple users hitting the same URL are served from this cache.

The Client-Side Router Cache lives entirely in the browser’s memory. When a user navigates between routes, Next.js fetches the RSC payload and stores it locally. If the user clicks “Back” or revisits a previously clicked tab, the UI renders instantly without sending a single network request to the server, preserving a native-app-like feel.

Q27
Describe the lifecycle of a Next.js App Router request involving both RSC and Client Components.

The lifecycle is drastically different from traditional React SPAs:

  1. The browser makes an HTTP request. The Node server begins executing the Server Components, making direct database queries.
  2. The server generates a specialized RSC payload and a standard HTML string.
  3. The HTML string is streamed to the browser to achieve an instant, non-interactive First Paint.
  4. The browser receives the RSC payload. React reconciles this payload to construct the Virtual DOM in memory without re-fetching data.
  5. Finally, the Client Component JavaScript bundles are downloaded. React “hydrates” the DOM, attaching event listeners to make it interactive.
Q28
How do Server Actions trigger revalidation in Next.js without full page reloads?

When a Server Action completes a database mutation, you can invoke revalidatePath('/route') or revalidateTag('cache-tag'). This instructs the Next.js backend to purge its server-side cache for that specific data.

Because the Server Action was initiated from the client, Next.js seamlessly re-runs the Server Components for the current route and streams the updated RSC payload down in the response of the POST request. React diffs this payload and patches the specific DOM elements that changed, completely avoiding a harsh browser refresh.

Q29
How do you share state between multiple Client Components across different branches of a Server Component tree?

You cannot initialize React Context inside a Server Component because RSCs run once on the server and do not hold ongoing state. Attempting to use createContext in a server file will throw an error.

To bypass this, you create a dedicated Client Component wrapper (e.g., <ThemeProvider>) that initializes the Context. You then wrap your Server Components (usually in the layout.tsx file) with this provider. The Server Components can fetch initial data and pass it as a serializable prop to the Provider, which then broadcasts the state to all deeply nested Client Components.

Q30
What is Streaming SSR and how does it improve Time to First Byte (TTFB)?

Standard SSR (used by older Next.js or Express apps) acts like a bottleneck: it waits for the absolute slowest API call to resolve before sending the <html> string to the browser. If a database query takes 3 seconds, TTFB is 3 seconds.

Streaming SSR utilizes HTTP chunked transfer encoding. Next.js instantly flushes the static layout (headers, navigation) to the browser. As asynchronous Server Components resolve inside <Suspense> boundaries, Next.js streams HTML fragments and tiny inline script tags into the still-open HTTP connection. These scripts instruct the browser to dynamically insert the resolved HTML into the correct placeholder, drastically lowering TTFB and keeping users engaged.

4. Advanced State & Micro-frontends

Q31
How does Redux Toolkit (RTK) solve the architectural pitfalls of legacy Redux?

Legacy Redux suffered from massive boilerplate (constants, action creators, reducers spread across files) and the high risk of accidental state mutation. RTK solves this primarily through createSlice, which auto-generates action creators and action types simultaneously.

Architecturally, the biggest shift is RTK’s integration of Immer.js. Immer wraps the state in a JavaScript Proxy. This allows developers to write code that *looks* like mutable state updates (e.g., state.push(item)), but Immer intercepts the mutation and safely produces a perfectly immutable next state under the hood.

Real-World Scenario: In a deep e-commerce cart structure, updating a nested item’s quantity used to require complex spread operators { ...state, cart: { ...state.cart, items: [...] } }. With RTK, the mutation is direct and clean, preventing subtle UI tearing bugs caused by mutated references.
import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: 0 },
  reducers: {
    // RTK + Immer allows "mutating" logic safely
    updateQuantity(state, action) {
      const item = state.items.find(i => i.id === action.payload.id);
      if (item) {
        item.quantity = action.payload.quantity; // Looks mutable, is actually immutable!
      }
    }
  }
});
export const { updateQuantity } = cartSlice.actions;
Q32
When would an Architect choose RTK Query over React Query (TanStack) or SWR?

React Query and SWR are fantastic standalone libraries for server-state caching. However, an architect chooses RTK Query when the application already heavily relies on Redux for complex client-side state, and that client state needs to tightly couple with the server state.

Because RTK Query generates Redux reducers and middleware automatically, you can listen to RTK Query cache events (like a successful API fetch) inside your standard Redux slices to trigger synchronous client-side UI workflows without bridging two separate state management systems.

// In a standard Redux slice, listening to an RTK Query endpoint:
extraReducers: (builder) => {
  builder.addMatcher(
    api.endpoints.purchaseOrder.matchFulfilled,
    (state, action) => {
      // Synchronously clear the client-side cart when the server purchase succeeds
      state.cartItems = []; 
      state.isCheckoutModalOpen = false;
    }
  );
}
Q33
Explain the difference between Atomic State (Jotai/Recoil) and Proxy State (Valtio/MobX) for high-frequency updates.

Atomic State (Jotai): State is broken into tiny, independent pieces (“atoms”). A component only subscribes to the specific atoms it needs. It solves the top-down re-render problem of Context because updating one atom only re-renders the components actively listening to it. It is explicit and declarative.

Proxy State (Valtio/MobX): The entire state object is wrapped in a JS Proxy. The proxy intercepts property access (`get`) to automatically track which component reads which property. When a property mutates (`set`), it precisely triggers a re-render only for the components that read that specific property. It is highly implicit and imperative.

Real-World Scenario: If you are building a Figma clone with 10,000 draggable shapes on a canvas. Using Context/Redux, dragging one shape might re-render the whole canvas. Atomic state maps each shape to its own atom. Proxy state tracks the exact X/Y coordinate accessed by the shape component. Both prevent the 9,999 other shapes from re-rendering during a drag event.
Q34
How does Webpack 5 Module Federation enable React Micro-frontends architecturally?

Historically, micro-frontends required building separate apps and mashing them together via IFrames or fragile NGINX routing. Module Federation allows multiple independent Webpack builds to share code and dynamically load chunks from each other at runtime over the network.

Architecturally, the most critical configuration is the shared dependencies array. If the “Host App” and “Remote App” both bundle their own copy of `react` and `react-dom`, React will crash with a “multiple instances of React” invariant violation (Hooks will fail). Module Federation negotiates this, ensuring the Host injects its singleton instance of React into the Remote app.

// webpack.config.js (Remote App)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'checkoutApp',
      filename: 'remoteEntry.js',
      exposes: { './CheckoutWidget': './src/CheckoutWidget' },
      shared: {
        react: { singleton: true, requiredVersion: '^18.2.0' },
        'react-dom': { singleton: true },
      },
    }),
  ],
};
Q35
What are the strategies for sharing state between disparate micro-frontends without tight coupling?

Micro-frontends should ideally be totally decoupled. If Team A’s Cart app crashes, Team B’s Catalog app should still work. Therefore, tightly coupling them to a single monolithic Redux store defeats the purpose.

  1. The URL (Best): Pass IDs or search filters via query parameters. It’s universally understood and inherently decoupled.
  2. Custom Browser Events: Use the native CustomEvent API. The Catalog dispatches an ‘ADD_TO_CART’ event; the Cart listens globally and updates itself.
  3. Shared Zustand/Zustand Store: If heavy state sharing is unavoidable, create a tiny, third micro-frontend that purely exposes a Zustand store via Module Federation, and have both apps subscribe to it.
Q36
Why is Zustand gaining traction over Redux in modern React Architectures?

Zustand solves several developer experience and performance issues inherent to Redux:

  • No Context Provider: Redux requires wrapping your app in <Provider store={store}>, tying the store to the React tree. Zustand stores are standard JS modules that live outside the React tree, meaning you can easily read/write state inside non-React files (like an Axios interceptor).
  • Transient Updates: Zustand allows you to subscribe to state changes without forcing a React component to re-render, which is crucial for 60FPS animations or scroll-syncing.
  • Zero Boilerplate: No actions, no reducers, no dispatching required. Just functions that mutate state.
import { create } from 'zustand';

// Store lives entirely outside the React tree
const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));

function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return <h1>{bears} around here ...</h1>;
}
Q37
Why does React Context cause performance bottlenecks, and how do you use Context strictly for Dependency Injection?

React Context is not a state management tool; it is a dependency injection mechanism. When a Context Provider’s value prop changes, React forces every single component that calls useContext() for that provider to re-render, bypassing React.memo entirely.

Real-World Scenario: If you put high-frequency WebSocket data inside Context, your entire app will lag. To fix this, you pass a Store Instance (or an Event Emitter) into the Context, not the data itself. The instance reference never changes, preventing Context re-renders. Components then use useSyncExternalStore to subscribe only to the specific slices of data they care about from that injected store.
Q38
What is State Normalization, and how does `createEntityAdapter` in RTK facilitate it?

In deep, nested JSON responses (e.g., a Post with an array of Comments, each with an Author), updating an author’s profile picture requires finding and mutating every instance of that author buried in the tree. This is incredibly slow and complex.

Normalization flattens the state. It stores entities in an object dictionary keyed by ID ({ id1: {...}, id2: {...} }), and uses arrays of IDs to manage relationships. RTK’s createEntityAdapter provides pre-built reducers (like upsertOne, removeMany) and memoized selectors to instantly manage flat, normalized state tables without writing custom mapping logic.

Q40
Why should the URL be considered a First-Class State Manager in React architectures?

A common anti-pattern is storing search queries, active tabs, and pagination data in Redux or useState. If a user finds a specific dashboard view and sends the link to a coworker, the coworker will see the default layout because the state was trapped in the sender’s local browser memory.

Architects promote the URL (Search Params) to be the Single Source of Truth for navigational state. The React component reads useSearchParams() to render the UI, and to update state, it pushes a new route via the history API. This guarantees that any complex UI view is 100% shareable, reproducible, and indexable by search engines.

Q39
How do you architect complex Side Effects (like token refresh queues) using RTK Listener Middleware?

Historically, Redux used Thunks (too simple for complex flows) or Sagas (complex Generator function syntax). RTK introduced Listener Middleware, a lightweight alternative to Sagas. It allows you to run imperative, asynchronous logic in response to specific dispatched actions.

If an API call fails due to an expired token, the Listener catches the specific “rejected” action, pauses all other outbound queries, dispatches a token refresh API call, and upon success, re-dispatches the original failed queries.

import { createListenerMiddleware } from '@reduxjs/toolkit';

const listenerMiddleware = createListenerMiddleware();

listenerMiddleware.startListening({
  matcher: api.endpoints.getUser.matchRejected,
  effect: async (action, listenerApi) => {
    if (action.payload.status === 401) {
      // Pause further processing, trigger refresh token
      const success = await listenerApi.dispatch(refreshTokenRoute());
      if (success) {
        // Retry original action
        listenerApi.dispatch(api.endpoints.getUser.initiate()); 
      }
    }
  }
});

5. Profiling & Memory Management

Q41
How do you identify memory leaks in a React application using Chrome DevTools?

An architect approaches memory leaks systematically using the Chrome DevTools “Memory” tab. The core technique is the 3-Snapshot Method. You take Snapshot 1 at baseline. You perform the suspected action (e.g., opening and closing a complex data grid component). You take Snapshot 2. You repeat the action and take Snapshot 3.

You then filter Snapshot 3 for objects allocated between Snapshots 1 and 2. Specifically, you search for “Detached DOM elements” or instances of your React components (like `DataGrid`). If the component is unmounted from the UI but still exists in the heap snapshot, you have successfully identified a leak.

Real-World Scenario: A charting dashboard crashes after 30 minutes. Snapshots reveal 500 detached `<canvas>` elements. The architect discovers the charting library is attaching global `window.addEventListener(‘resize’)` handlers on mount, but failing to remove them on unmount, keeping the unmounted canvas elements forever locked in memory.
Q42
Explain the impact of closure scope on memory retention in long-lived React components.

Memory leaks in React rarely originate from the virtual DOM itself; they almost exclusively stem from poorly managed closure scopes inside useEffect or external event subscriptions. When a component registers an interval or listener, the callback function forms a closure over the component’s lexical scope.

If the component unmounts but the listener isn’t cleanly removed, the JavaScript engine’s Garbage Collector (GC) cannot free the memory. The browser’s native API still holds a reference to that callback—and by extension, the entire component state captured in the closure.

import { useEffect, useRef, useState } from 'react';

export function DeliveryCountdown({ orderId }) {
  const [timeLeft, setTimeLeft] = useState(1800);
  const timerRef = useRef(null);

  useEffect(() => {
    // Architect tip: Store the interval ID in a ref to guarantee clearing 
    // even if the component re-renders rapidly before unmounting.
    timerRef.current = setInterval(() => {
      setTimeLeft((prev) => (prev <= 1 ? 0 : prev - 1));
    }, 1000);

    // CRITICAL: The cleanup function that prevents the memory leak
    return () => clearInterval(timerRef.current);
  }, [orderId]);

  return <div>{Math.floor(timeLeft / 60)} mins remaining</div>;
}
Q43
What is the technical difference between Shallow and Retained heap size?

When analyzing a Heap Snapshot, understanding these two columns is critical for pinpointing the root cause of a leak.

Shallow Size: The memory allocated for the object itself. For a React component, this is usually tiny—just the memory required to hold the primitive values and the memory pointers to its children.

Retained Size: The massive amount of memory that would be freed if that specific object, and all the dependent objects it points to, were deleted. If a tiny 50-byte event listener closure holds a reference to a 50MB Redux data array, its shallow size is 50 bytes, but its retained size is 50MB.

Q44
How does the React DevTools Profiler calculate the “base duration” versus “actual duration”?

The React Profiler records how long components take to render. It splits this into two metrics to help you judge the effectiveness of your memoization (React.memo, useMemo).

Actual Duration: The literal time spent rendering the component and its children for the current specific update. If children bailed out due to memoization, this number will be low.

Base Duration: The estimated time it would take to completely render the component tree from scratch (like on initial mount) without any memoization or bailout optimizations. If your Actual Duration is consistently near your Base Duration during updates, your memoization strategy is failing.

Q45
Explain how to use the PerformanceObserver API to track custom React render metrics.

Relying purely on React DevTools locally doesn’t tell you how your app performs on a low-end Android phone in production. React outputs marks using the native browser User Timing API. Architects instantiate a PerformanceObserver to listen for specific “mark” and “measure” events programmatically.

Real-World Scenario: You want to track the exact millisecond duration of React’s “commit phase” to see if DOM mutations are causing frame drops. You write a PerformanceObserver to intercept React’s native profiling marks and stream that data to Datadog or New Relic for fleet-wide telemetry.
// Tracking React rendering bottlenecks in production
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // React tags its internal performance marks with an emoji or specific prefix
    if (entry.name.includes('⚛️')) {
      console.log(`React Phase: ${entry.name} took ${entry.duration}ms`);
      // sendToAnalytics(entry.name, entry.duration);
    }
  }
});

// Observe custom marks and measures
observer.observe({ entryTypes: ['mark', 'measure'] });
Q46
What are the negative performance implications of excessively deep component trees?

Developers often create overly nested component trees (e.g., wrapping every element in multiple HOCs or context providers). Even if these components are purely presentational and execute quickly, a massive tree has severe hidden costs.

React must allocate a Fiber node object for every single component. A tree with 10,000 nodes means 10,000 objects in the V8 JS heap. During reconciliation, React must recursively traverse this massive linked list. This creates constant CPU overhead, inflates the Javascript memory heap, and triggers aggressive Garbage Collection (GC). GC events are “Stop-The-World”—they freeze the main thread, causing severe scrolling stutters on mobile devices.

Q47
How do you optimize React applications for low-end mobile devices regarding JS parse times?

Mobile CPUs severely struggle to decompress and parse massive JavaScript bundles. If you ship a 5MB JS bundle, a modern iPhone parses it in 200ms, but a low-end Android device might take 3-4 seconds, paralyzing the UI.

Architectural optimizations require aggressive Route-level code splitting (via React.lazy or Next.js dynamic imports). You must avoid “Barrel Files” (export * from './components') which trick Webpack into bundling the entire component library when only a single button is needed. Finally, migrating heavy dependencies (like Markdown parsers or Date formatters) to React Server Components (RSCs) physically removes them from the mobile device’s parse queue.

Q48
Explain the “deopt” scenarios in V8 (Chrome’s JS engine) that affect React performance.

V8 compiles JavaScript down to ultra-fast machine code by making assumptions about object shapes (Hidden Classes). If a function always receives an object with { x: number, y: number }, V8 optimizes it. This is called a “Monomorphic” function.

If a React component dynamically deletes keys from its state (delete state.userId) instead of setting them to null, it changes the physical shape of the object. V8 detects this, throws away the optimized machine code, and falls back to slow, interpreted execution (a “Deopt”). To keep React fast, architects mandate that state object shapes must remain completely uniform throughout their lifecycle.

// BAD: Changes object shape, forces V8 engine de-optimization
const handleLogout = () => {
  const newState = { ...user };
  delete newState.token; 
  setUser(newState);
};

// GOOD: Preserves object shape, keeps V8 execution lightning fast
const handleLogout = () => {
  setUser(prev => ({ ...prev, token: null }));
};
Q49
How do you implement aggressive code splitting without causing layout thrashing or poor UX?

The naive approach to code splitting is lazily loading heavy components right when they render. This results in the user clicking a button, waiting for a network request, and staring at a blank screen or a jerky layout shift.

Real-World Scenario: In an e-commerce app, the `CheckoutModal` is a heavy 200kb chunk containing Stripe libraries. Instead of loading it only when the “Buy” button is clicked, an architect attaches a prefetch trigger to the `onMouseEnter` event of the button. The chunk downloads silently in the 300ms it takes the user’s brain to register the hover and click, ensuring the modal opens instantly with zero layout shift.
Q50
What is Layout Thrashing, and how do you prevent it when using useLayoutEffect?

Layout Thrashing (or Forced Synchronous Layout) happens when you repeatedly alternate between reading DOM measurements (like element.offsetHeight) and writing DOM mutations (like element.style.height = '100px') in a loop or across uncoordinated components.

When you read a measurement, the browser assumes the layout is clean. If you write a mutation, the layout is dirtied. If you read again immediately, the browser is forced to halt JS execution and synchronously recalculate the entire page layout to give you the correct measurement. Architects prevent this by enforcing strict separation: batch all read operations first, store the values, and execute all write operations afterward (often utilizing libraries like fastdom to orchestrate this).

6. Internals & Custom Renderers

Q51
What is the react-reconciler package and how do you architect a custom renderer?

React is fundamentally broken into two distinct parts: the Reconciler (which handles the Fiber engine, state updates, diffing, and component lifecycles) and the Renderer (which applies those updates to a specific environment, like the DOM or Native iOS).

The react-reconciler is an NPM package that exposes the core Fiber engine. To build a custom renderer, an architect feeds a HostConfig object into this reconciler. The reconciler handles all the complex state management, and whenever the Virtual DOM changes, it calls your HostConfig methods to mutate your specific environment.

Real-World Scenario: Your team is building a complex Command Line Interface (CLI) dashboard for monitoring server health. Instead of writing messy imperative Node.js terminal strings, you architect a custom renderer (similar to the ink library) that translates React JSX components (like <Box> and <Text>) into terminal escape sequences.
import ReactReconciler from 'react-reconciler';

// The "translation layer" between React and your custom environment
const HostConfig = {
  createInstance(type, props) {
    if (type === 'text') return new TerminalTextNode(props.children);
    if (type === 'box') return new TerminalBoxNode(props);
  },
  appendInitialChild(parent, child) {
    parent.appendChild(child);
  },
  // ... dozens of other required mutation methods
};

const CustomRenderer = ReactReconciler(HostConfig);
CustomRenderer.render(<App />, terminalRootContainer);
Q52
How does a custom React renderer (like React Three Fiber) map React elements to non-DOM APIs?

Unlike react-dom, which knows that <div> maps to document.createElement('div'), a custom renderer must maintain a dictionary or factory mapping string types to specific target classes.

In React Three Fiber (R3F), when you write <mesh />, the HostConfig.createInstance method catches the string “mesh”. It does not create an HTML tag; instead, it instantiates a new THREE.Mesh() object. When React updates a prop like position={[1, 2, 3]}, the renderer intercepts this and executes threeObject.position.set(1, 2, 3).

Q53
Explain the primary role of the HostConfig object.

The HostConfig is the contract between React’s pure JavaScript logic and the target host environment. It forces the developer to provide concrete implementations for Abstract UI concepts.

It contains lifecycle hooks that map directly to the Commit Phase. For example: createInstance (called when a new element is mounted), commitUpdate (called when props change), removeChild (called on unmount), and createTextInstance. By implementing these specific methods, you teach React how to draw, update, and erase things in an environment it natively knows nothing about.

Q54
How did React Native legacy architecture bridge the JavaScript thread and the UI thread, and why was it a bottleneck?

In the legacy architecture, the JavaScript thread (running Hermes/JSC) and the Native UI thread (Java/Objective-C) were completely isolated. They could only communicate by passing messages over an asynchronous “Bridge”.

Every time React Native wanted to update a ``, it had to serialize the command into a massive JSON string, send it over the bridge, and wait for the native side to deserialize and execute it. Because this was asynchronous and batched, high-frequency events (like scrolling a list at 60 FPS or tracking a drag gesture) flooded the bridge, causing massive layout thrashing, dropped frames, and “white flashes” of unrendered UI.

Q55
What is the React Native Fabric architecture and how does it improve performance using JSI?

Fabric represents the total architectural rewrite of React Native. It completely eliminates the asynchronous JSON Bridge. Instead, it utilizes JSI (JavaScript Interface).

JSI allows C++ to expose native UI objects directly to the JavaScript engine’s memory space. JavaScript can now hold direct references to C++ Host Objects and call their methods synchronously. This allows React to mutate Native UI components exactly like it mutates the DOM in a browser—instantly and precisely—enabling flawlessly smooth 120 FPS animations without serialization overhead.

Real-World Scenario: You are building a complex bottom-sheet map UI that the user drags up and down. With JSI (via libraries like Reanimated 2+), the user’s touch coordinates update a Shared Value that exists directly in C++ memory. The UI thread reads this value synchronously, updating the bottom-sheet position perfectly in tandem with the user’s finger.
Q56
How do you implement your own event system within a custom React renderer?

React’s Synthetic Event system (onClick, onChange) does not exist in react-reconciler; it is bundled exclusively within react-dom. If you build a custom renderer for HTML5 Canvas, a `` does absolutely nothing natively because the Canvas is just one giant bitmap pixel array.

Architects must build a custom event delegation system. You attach native DOM mouse listeners to the global Canvas element. On click, you calculate the X/Y coordinates. You then use an algorithm (like Raycasting for 3D/WebGL, or geometric hit-testing for 2D) to figure out which of your React-managed instances intersects with those coordinates. Finally, you manually invoke the `onClick` prop stored on that instance.

Q57
Explain how React synthetic events are delegated to the root, and the critical architecture change in React 17.

React does not attach event listeners to individual DOM nodes to save memory. Instead, it uses Event Delegation. Prior to React 17, React attached a single event listener for every event type (click, keypress) directly to the global document object.

The React 17 Architectural Shift: React changed delegation from the document to the specific React Root Container (the div#root you pass to createRoot). This was a massive change for Micro-frontends. Previously, if you embedded a React 16 app inside a React 15 app on the same page, their document-level event listeners would clobber each other. Moving delegation to the root container safely isolates multiple React applications living on the exact same DOM tree.

Q58
What happens during the “mutation” phase of the commit phase in react-dom?

The Commit Phase is broken into three distinct sub-phases: Before Mutation, Mutation, and Layout.

During the Mutation Phase, React physically alters the DOM. It iterates over the Fiber nodes flagged with “Effect Tags” (calculated during the Render phase). It executes appendChild, removeChild, and updates className or style attributes. Crucially, this is also the exact moment React detaches old ref values and attaches new ref instances to the newly mutated DOM nodes.

Q59
How does React handle cross-browser normalization for input events?

Native DOM form events are highly fragmented across browsers (e.g., IE/Edge handling input differently than Safari). A native onInput event fires at different times than an onChange event depending on the OS.

React abstracts this chaos. When you use onChange in React, it isn’t simply binding to the native onChange. React’s internal event plugins monitor a combination of native events—input, keydown, keyup, paste, and proprietary browser events. It normalizes this data into a single, predictable SyntheticEvent, guaranteeing that an architect’s form logic behaves identically across all devices and browsers.

Q60
Can you use React’s reconciliation engine without a UI (e.g., for hardware orchestration)?

Yes. Fundamentally, React is not a UI library; it is an optimized state-machine engine that diffs trees over time. Any system that can be represented as a hierarchical tree of state can be managed by React.

Real-World Scenario: Architects use libraries like react-hardware to control IoT devices (like Arduino or Raspberry Pi). Instead of writing imperative loops to manage physical hardware state, you represent hardware components as JSX. A change in React state gracefully toggles actual electrical currents on and off.
import { render } from 'react-hardware';

function BlinkingLED({ isBlinking }) {
  // Instead of DOM nodes, this renderer maps to physical GPIO pins
  return (
    <pin pin={13} mode="OUTPUT" value={isBlinking ? 'HIGH' : 'LOW'} />
  );
}

// Renders the state tree directly to the serial port connected to the Arduino
render(<BlinkingLED isBlinking={true} />, '/dev/tty.usbmodem1411');

7. Advanced Patterns

Q61
Explain the “State Reducer” pattern and how it provides Inversion of Control.

When building highly reusable components (like a Select Dropdown for a UI library), hardcoding every possible edge case via boolean props (e.g., closeOnSelect={false}) bloats the component.

The State Reducer pattern solves this by giving the component’s consumer direct access to intercept state transitions. The component manages its own state via an internal useReducer, but accepts a stateReducer prop. Before applying any state change, the component passes the proposed change to the consumer’s reducer, allowing the consumer to modify or completely cancel the update.

Real-World Scenario: You are building a generic `` component. By default, it closes when an item is clicked. A specific consumer wants it to stay open if the user clicks “Select All”. Instead of adding a `stayOpenOnSelectAll` prop, the consumer intercepts the ‘ITEM_CLICKED’ action and forces the `isOpen` state to remain `true`.
// Inside the reusable library component
function useSelect(stateReducer = (state, action) => action.changes) {
  const [state, dispatch] = useReducer((state, action) => {
    const changes = internalReducer(state, action);
    // Inversion of Control: Let the user override our internal logic
    return stateReducer(state, { ...action, changes }); 
  }, initialState);
  // ...
}
Q62
How do you implement the “Dependency Injection” pattern in React without using Context?

While Context is commonly used for global Dependency Injection (DI), it forces the component to be deeply coupled to the React tree, making isolated Unit Testing difficult. True DI in React is achieved via Component Injection (Render Props) or passing service interfaces directly as props.

By passing the service/component as a prop, the component becomes a pure orchestrator. It knows what to execute, but relies on the parent to define how it executes.

// Generic Dashboard component receives its dependencies as props
export function Dashboard({ LoggerService, AnalyticsAdapter, ChartComponent }) {
  const handleLoad = () => {
    LoggerService.info('Dashboard mounted');
    AnalyticsAdapter.trackEvent('view_dashboard');
  };

  return (
    <div onLoad={handleLoad}>
      <!-- We don't care if this is a D3 Chart or a Chart.js Chart -->
      <ChartComponent data={data} /> 
    </div>
  );
}
Q63
Describe the “Event Sourcing” pattern applied to React application architecture.

Standard state management stores the current state. Event Sourcing never mutates a state object; instead, it stores an append-only array of immutable events (e.g., [{ type: 'ADD_ITEM', id: 1 }, { type: 'CHANGE_QTY', id: 1, qty: 5 }]). The current UI state is derived on the fly by reducing (replaying) these events from the beginning.

Real-World Scenario: Building a browser-based graphic design tool (like Canva). Implementing infinite Undo/Redo by cloning the entire canvas state on every mouse move would destroy the heap memory. Using Event Sourcing, you simply append “moved 5px left” to the array. “Undo” simply means recalculating the canvas while ignoring the last event in the array.
Q64
How do you build a completely Headless UI component (like Radix UI or Downshift)?

Headless UI components provide zero markup and zero CSS. They exclusively encapsulate complex logic, state machinery, and W3C accessibility (ARIA) attributes. They expose this logic via custom hooks or the Render Props pattern.

The consumer uses the hook, receives an object of event handlers and ARIA attributes (like aria-expanded or onKeyDown), and manually spreads (...) them onto their own styled HTML elements, completely decoupling the brains from the beauty.

// The Headless Hook (Library Code)
export function useAccordion() {
  const [isOpen, setIsOpen] = useState(false);
  return {
    isOpen,
    triggerProps: {
      onClick: () => setIsOpen(!isOpen),
      'aria-expanded': isOpen,
      'aria-controls': 'accordion-content',
    },
    contentProps: {
      id: 'accordion-content',
      hidden: !isOpen,
    }
  };
}

// The Consumer (App Code)
function MyStyledAccordion() {
  const { triggerProps, contentProps } = useAccordion();
  return (
    <div className="tailwind-wrapper">
      <button className="bg-blue-500" {...triggerProps}>Toggle</button>
      <div className="p-4" {...contentProps}>Content inside</div>
    </div>
  );
}
Q65
Explain how to manage complex Z-index stacking contexts in a scalable React application.

Architects forbid hardcoding z-index: 9999. Z-indexes only work within their local Stacking Context (created by position: relative or transform). If a parent has z-index: 1, a child modal with z-index: 99999 will still be trapped underneath an adjacent sibling with z-index: 2.

The architectural solution is React Portals. You create a sibling <div id="portal-root"> at the absolute bottom of your <body>, completely outside your React app root. Whenever a Component needs to break out (Modals, Tooltips, Toasts), you use ReactDOM.createPortal(child, domNode). The logic stays in your component tree, but the physical HTML is injected at the end of the document, naturally rendering on top of everything without fighting Z-index wars.

Q66
What is the Actor model (e.g., XState) and how does it differ from traditional reducers in React?

Traditional state machines (like `useReducer`) are passive; they only calculate the next state when an event is dispatched. They cannot easily manage asynchronous side effects or time.

The Actor Model allows you to spawn independent “Actors” that run concurrently. An Actor maintains its own state and can send and receive asynchronous messages. If you build a complex flow using XState, the state machine itself can trigger an API call, wait for the response, transition to an ‘error’ state, and send a message back to the React component, completely decoupling orchestration logic from the view.

Q67
How do you implement robust Drag and Drop functionality natively without heavy external libraries?

Heavy libraries (like react-beautiful-dnd) often bloat bundles. For many use cases, the native HTML5 Drag and Drop API is sufficient when orchestrated correctly with React state.

You apply the draggable={true} attribute to the source element. In onDragStart, you store the dragged item’s ID in React state. On the target container, you must call e.preventDefault() inside the onDragOver event; otherwise, the browser forbids dropping. Finally, in the target’s onDrop event, you read the dragged ID from state and trigger your reducer/state mutation to move the item.

Q68
Discuss strategies for building accessible, keyboard-navigable grid/table components.

If you build an Excel-like data grid with 1,000 cells and apply tabindex="0" to all of them, a user relying on a keyboard will have to press “Tab” 1,000 times to move past the table. This is an accessibility violation.

Real-World Scenario: Architects use the Roving Tabindex pattern. The grid wrapper catches keyboard events. At any given moment, only exactly one cell in the grid has tabindex="0". Every other cell has tabindex="-1". When the user presses the Arrow Keys, a custom React hook intercepts the keystroke, updates the active coordinate state (X/Y), shifts the `tabindex=”0″` to the new cell, and executes `.focus()` programmatically. The user can traverse the whole grid via arrows, but pressing Tab skips the entire table instantly.
Q69
How do you handle complex, multi-step wizards with branch logic and persistent state?

Wizards that rely on const [step, setStep] = useState(1) break immediately if the user accidentally hits the browser’s “Back” button (it navigates them completely away from the app instead of back one step). It also makes deep linking impossible.

Architects treat Wizards as Nested Routes or URL Search Parameters (e.g., /onboarding?step=profile). The central state (the accumulated form data) is held in a higher-order Context or Zustand store. The routing library handles the “Next” and “Back” branch logic by pushing new URL parameters, perfectly aligning the browser history with the Wizard steps.

Q70
What is the “Isomorphic” component pattern and what hydration issues does it present today?

Isomorphic (or Universal) components execute the exact same JavaScript code on the Node.js server to generate the initial HTML, and then again in the browser to hydrate the DOM.

The danger is Hydration Mismatches. If an isomorphic component renders new Date().getTime(), the Node server evaluates the time at 12:00 PM. The browser downloads the JS and evaluates the time at 12:01 PM. Because the browser’s virtual DOM (12:01) does not match the server’s HTML (12:00), React throws a hydration error and forces a costly synchronous re-render of the entire tree. Architects bypass this using useEffect (which only runs on the client) to set dynamic client-specific data after the initial consistent render.

8. Testing Strategy & CI/CD

Q71
How do you effectively test React components that rely heavily on `requestAnimationFrame` or `IntersectionObserver`?

Most React Unit tests run in JSDOM (via Jest or Vitest). JSDOM is a headless JavaScript implementation of the DOM, but it completely lacks a rendering engine. Therefore, layout APIs like `IntersectionObserver` and animation APIs like `requestAnimationFrame` literally do not exist or silently fail.

Architects solve this in two ways. The first is to mock the global API in the test setup file, manually triggering the observer callbacks to simulate scrolling. The better architectural shift is to move away from JSDOM entirely for visual components and use Cypress Component Testing or Playwright Component Testing, which mounts the isolated React component inside a real Chrome/Webkit browser engine, executing native layout APIs flawlessly.

// Vitest/Jest setup: Mocking IntersectionObserver for JSDOM
class MockIntersectionObserver {
  constructor(callback) {
    this.callback = callback;
  }
  observe(element) {
    // Manually trigger the callback to simulate an element entering the viewport
    this.callback([{ isIntersecting: true, target: element }]);
  }
  unobserve() {}
  disconnect() {}
}
global.IntersectionObserver = MockIntersectionObserver;
Q72
Explain the concept of “Visual Regression Testing” and its primary architectural pitfalls in React.

Visual Regression Testing (via tools like Percy or Chromatic) renders your components in a headless browser, takes screenshots, and compares them pixel-by-pixel against a baseline image from the `main` branch. If a CSS change shifts a button 2 pixels, the test fails.

The main pitfall is brittleness and false negatives. Dynamic data (like rendering `new Date()`), slight OS font anti-aliasing differences (Mac vs. Linux CI servers), and CSS animations cause tests to fail constantly even when the UI is correct. Architects mitigate this by mocking all dates/times globally, forcing a specific random seed, and disabling all CSS animations (`* { animation: none !important; }`) injected during the test run.

Q73
How do you mock WebSockets or Server-Sent Events (SSE) in a React testing environment?

You cannot test a component that connects to `ws://localhost:8080` in CI without spinning up a real backend server, which makes tests slow and flaky. You must intercept the native `WebSocket` object directly in the testing environment.

Real-World Scenario: Testing a live chat or inventory tracker. Architects use libraries like `mock-socket`. It overrides the global browser `WebSocket` constructor. When the React component attempts to connect, it connects to the mock server in memory. The test script can then push fake JSON messages through the mock socket to assert that the React UI updates correctly without any real network traffic.
Q74
Discuss the strategies for testing React Server Components (RSC) and Server Actions in Next.js.

React Testing Library (RTL) traditionally mounts components in the browser (JSDOM). Because React Server Components execute strictly in Node.js and often contain raw database queries, rendering them in JSDOM fails instantly.

The Strategy: Do not try to unit test the rendering of an RSC. Instead, test the architecture in three layers:

  1. Unit Test: Extract the data-fetching logic and Server Actions into isolated pure functions and test them using standard Node/Vitest test runners.
  2. Component Test: Extract the interactive UI into Client Components and test them using RTL/JSDOM.
  3. E2E Test: Use Playwright to visit the actual route. This tests the integration—that the Server Component successfully passed the RSC payload to the Client Component.
Q75
How do you measure and enforce test coverage on asynchronous, lazy-loaded chunks?

If your application heavily uses `React.lazy` or Next.js `dynamic`, standard synchronous test runners will unmount the component before the lazy chunk finishes downloading. As a result, Istanbul (the coverage tool) reports 0% coverage on those dynamically imported files, even if they work perfectly.

Architects fix this by wrapping the render call in an asynchronous `act()` block and explicitly awaiting the resolution of the Suspense boundary using `findByText` or `findByTestId`. This forces the JSDOM event loop to wait for the mocked chunk to resolve, ensuring the execution path runs and coverage is accurately recorded.

Q76
What is “Mutation Testing,” and how would you apply it to a React codebase?

Traditional coverage only tells you if a line of code was executed, not if the test is actually meaningful. A test without an `expect` assertion provides 100% coverage but catches zero bugs.

Mutation Testing (using tools like Stryker) tests the quality of your test suite. It automatically alters your source code (e.g., changing `amount > 10` to `amount < 10` or swapping `+` for `-`) and runs your test suite. If your tests pass despite the broken code, the mutation "survived," exposing a weak or missing assertion. If the test fails, the mutation was "killed," proving the test is robust.

Q77
How do you use Playwright/Cypress to intercept and mock GraphQL queries predictably?

Unlike REST APIs, where every endpoint has a unique URL (`/api/users`, `/api/orders`), GraphQL routes all traffic through a single POST endpoint (`/graphql`). You cannot simply mock the URL; you must intercept the network request, parse the JSON body, read the `operationName`, and return conditional mock data.

// Playwright GraphQL Mocking
await page.route('**/graphql', async (route) => {
  const request = route.request();
  const postData = JSON.parse(request.postData());

  if (postData.operationName === 'GetProfile') {
    // Intercept and return fake profile data
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ data: { user: { name: 'Admin', role: 'architect' } } })
    });
  } else {
    // Let other queries (like analytics) pass through to the real server
    await route.continue();
  }
});
Q78
Explain how to test for memory leaks automatically in a CI pipeline using Playwright?

Manual heap snapshots are great for debugging, but memory leaks will continually regress without automated enforcement. Architects use Playwright connected to Chrome via the CDP (Chrome DevTools Protocol) to automate leak detection in CI.

Real-World Scenario: A script automates logging in, navigating to a heavy dashboard, navigating back to home, and repeating this loop 10 times. After the loops, the script issues a direct CDP command (`HeapProfiler.collectGarbage`) to force a GC cycle. It then measures `performance.memory.usedJSHeapSize`. If the heap size is significantly larger at the end of loop 10 than loop 1, the CI build fails automatically, preventing a leak from reaching production.
Q79
How do you test accessibility (a11y) automatically within Jest/RTL?

Manual accessibility audits are slow. Architects enforce baseline a11y compliance by integrating the `jest-axe` library directly into the React Testing Library suite.

During a unit test, you render the component to the JSDOM, pass the resulting HTML container into the `axe()` function, and assert `toHaveNoViolations()`. This instantly catches missing `aria-labels`, invalid ARIA roles, duplicated IDs, and contrast issues before the code is ever merged.

import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);

it('should have no accessibility violations', async () => {
  const { container } = render(<ComplexDataGrid data={mockData} />);
  
  // Scans the DOM output against WCAG standards
  const results = await axe(container);
  
  expect(results).toHaveNoViolations();
});
Q80
What are the architectural best practices for handling flaky E2E tests in a large React project?

E2E tests in Playwright or Cypress become “flaky” (randomly failing) due to asynchronous unpredictability: network latency, CSS animation delays, or slow React hydration. Architects enforce strict rules to eradicate flakiness:

  • Never use hardcoded sleeps: Avoid `await page.waitForTimeout(2000)`. Always wait for explicit visual states, like `await expect(locator).toBeVisible()`.
  • Mock third-party scripts: Block requests to Google Analytics, Intercom, or ad networks that mutate the DOM unexpectedly and slow down the page.
  • Wait for Hydration: React might render the HTML, but clicks fail if JS hasn’t hydrated. Expose a global `window.__REACT_HYDRATED__ = true` flag on mount, and have the E2E test wait for this flag before interacting.

9. Webpack, Vite & Build Optimization

Q81
Explain the architectural difference between Webpack and Vite, and why Vite provides faster local development.

Webpack is a Bundler. When you start the dev server, it crawls your entire application tree, compiles every module, resolves dependencies, and packs them into a single massive JS file in memory before the browser can render anything. As the app grows, boot time crawls to a halt.

Vite utilizes Native ESM (ECMAScript Modules). It does not bundle the code during development. It serves your source code directly to the browser over HTTP as individual native ES modules. When the browser requests a specific file, Vite compiles only that file on demand using esbuild (written in Rust/Go). This makes local boot times near-instantaneous, regardless of the application’s overall size.

Q82
Explain Tree Shaking under the hood. Why might a React library fail to tree-shake properly?

Tree Shaking (Dead Code Elimination) relies exclusively on static ES6 module syntax (import and export). During the build, the bundler statically analyzes the AST (Abstract Syntax Tree) to trace exactly which exports are used. Unused exports are stripped from the final bundle.

Libraries fail to tree-shake for two reasons: First, if they are compiled to CommonJS (require), which is dynamic and cannot be statically analyzed. Second, due to Side Effects. If a library file mutates a global object (e.g., window.MyPolyfill = true) just by being imported, the bundler cannot safely delete that file even if no functions from it are explicitly called. Architects fix this by enforcing the "sideEffects": false flag in the library’s package.json.

Q83
How do you configure Webpack/Vite for optimal chunking strategies in a massive React app?

Shipping a monolithic 5MB `main.js` kills Time to Interactive (TTI). Architects implement strict chunking boundaries to maximize browser caching.

Real-World Scenario: An architect splits the build into three distinct chunks:
1. Framework Chunk: `react`, `react-dom`, and routing. This rarely changes and can be cached by the browser for a year.
2. Vendor Chunk: UI libraries (like MUI or Tailwind). Changes occasionally.
3. Feature Chunks: The actual application code, split aggressively by Route, which changes on every deployment.
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          if (id.includes('node_modules')) {
            if (id.includes('react') || id.includes('react-dom')) {
              return 'vendor-react'; // Framework chunk
            }
            return 'vendor'; // Other libraries
          }
        }
      }
    }
  }
});
Q84
What is the purpose of AST manipulation plugins (like SWC or Babel), and why are architects moving to SWC?

AST (Abstract Syntax Tree) tools intercept source code and rewrite it before the browser sees it. For React, this means compiling JSX into React.createElement or stripping out TypeScript types.

Historically, Babel (written in JavaScript) handled this. However, Babel is single-threaded and bound by JS execution speeds. Architects are moving to SWC (Speedy Web Compiler) because it is written in Rust. It executes natively on multi-core CPUs, transforming massive React codebases up to 20x faster than Babel, radically reducing CI/CD pipeline deployment times.

Q85
How do you implement Service Workers alongside React to achieve true Offline-First capability?

An offline-first architecture intercepts all network requests before they leave the browser using a Service Worker (usually generated via Google Workbox in the Webpack/Vite config).

The Service Worker applies specific caching strategies. For static React assets (JS/CSS chunks), it uses Cache First (serving files from the local disk instantly). For critical API data, it uses Network First, falling back to Cache. If the user loses connection on the subway, the API fetch fails, but the Service Worker intercepts the failure and returns the last known JSON payload from the Cache Storage API, allowing the React UI to remain fully functional.

Q86
Explain the mechanics of Hot Module Replacement (HMR) and React Fast Refresh.

Traditional HMR injected new JavaScript into the browser but often destroyed React component state because it couldn’t map the new code to the existing Virtual DOM nodes. You would save a file, and your filled-out form would suddenly blank out.

React Fast Refresh fixes this. It is deeply integrated with the React Reconciler. When a component file changes, the bundler sends the new function over a WebSocket. Fast Refresh instructs React to re-render that specific component subtree. Crucially, it matches the hook signatures; if the useState order hasn’t changed, React seamlessly maps the old state to the newly injected component logic without losing the user’s data.

Q87
What is WebAssembly (Wasm) and how do you seamlessly integrate it into a React rendering cycle?

WebAssembly is a binary instruction format that runs in the browser at near-native C++ speeds. JavaScript is heavily bottlenecked by garbage collection and JIT compilation during intense math operations.

Real-World Scenario: A React app needs to apply a complex blur filter to a 4K image. Doing this in pure JS blocks the main thread, freezing the UI. The architect compiles a C++ image processing library to `.wasm`. In React, they asynchronously load the Wasm module inside a `useEffect`, store the executable instance in a Ref, and invoke it instantly without blocking the React rendering cycle.
import { useEffect, useState } from 'react';

export function WasmFilter() {
  const [wasmModule, setWasmModule] = useState(null);

  useEffect(() => {
    // Asynchronously instantiate the WebAssembly binary
    WebAssembly.instantiateStreaming(fetch('/imageProcessor.wasm'))
      .then(obj => setWasmModule(obj.instance.exports));
  }, []);

  const applyFilter = (imageData) => {
    if (!wasmModule) return;
    // Execute C++ logic directly from React
    wasmModule.applyBlur(imageData, imageData.length);
  };

  return <button onClick={applyFilter}>Apply Heavy Filter</button>;
}
Q88
How do you analyze and fix duplicate dependencies bloating your bundle?

Architects regularly run webpack-bundle-analyzer (or rollup-plugin-visualizer). A common massive issue is seeing two different versions of the same library (e.g., `lodash@4.1` and `lodash@4.17`) packed into the final build because two different third-party React components depend on strictly different versions.

To fix this and force a singleton instance, architects use the resolutions field in package.json (for Yarn) or overrides (for NPM). This forcibly overrides the dependency tree, instructing the bundler to resolve all requests for that library to one single, canonical version, stripping megabytes of duplicated code from the chunk.

Q89
Discuss the use of Module Preloading and Prefetching via Webpack magic comments.

When using `React.lazy`, the browser won’t even start downloading the chunk until the component is actually required to render, which causes a loading spinner.

Architects use Webpack Magic Comments to control browser priority heuristics. Adding /* webpackPrefetch: true */ to a dynamic import instructs Webpack to inject a <link rel="prefetch"> tag. The browser will silently download the chunk in the background only when it is completely idle. /* webpackPreload: true */ instructs the browser to download it immediately in parallel with the main bundle, reserving it for chunks required milliseconds after the initial load.

// Triggers a background download during browser idle time
const HeavyDashboard = React.lazy(() => import(
  /* webpackPrefetch: true */
  /* webpackChunkName: "dashboard-view" */
  './HeavyDashboard'
));
Q90
How do you inject build-time environment variables securely without exposing secrets to the client bundle?

React executes in the user’s browser, which is a fundamentally insecure environment. Any environment variable accessed via process.env.API_KEY on the client is string-replaced during the build process and is fully visible to anyone who inspects the compiled JS file.

Architects enforce a strict prefixing strategy (like NEXT_PUBLIC_ in Next.js or VITE_ in Vite). The bundler is configured to only inject variables with this prefix into the client build. True secrets (database passwords, private API keys) are never prefixed, ensuring the bundler completely ignores them, keeping them safely confined to the secure Node.js server executing the Server Components or API routes.

10. Security & Edge Cases

Q91
How do you prevent Cross-Site Scripting (XSS) when architecturally required to use dangerouslySetInnerHTML?

React automatically escapes text strings to prevent XSS. However, if you are building a CMS or Markdown parser, you must inject raw HTML using dangerouslySetInnerHTML. This bypasses React’s security, allowing attackers to inject <script> tags or <img onerror="stealToken()"> payloads.

Architects mitigate this by strictly enforcing a Sanitization layer (like DOMPurify). DOMPurify parses the dirty HTML string, builds a DOM tree in memory, and violently strips out any tags, attributes, or event handlers that are not explicitly whitelisted before passing it to React.

import DOMPurify from 'dompurify';

export function MarkdownRenderer({ dirtyHtml }) {
  // Never pass raw user input directly to React
  const cleanHtml = DOMPurify.sanitize(dirtyHtml, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'h1'],
    ALLOWED_ATTR: ['href'] // Only allow safe attributes
  });

  return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
}
Q92
Explain the architectural trade-offs between storing JWTs in localStorage vs. HttpOnly cookies in a React SPA.

Storing a JWT in localStorage makes it incredibly easy for the React app to read the token and attach it to API headers. However, if a single XSS vulnerability exists anywhere in your app (or a third-party dependency), malicious JS can instantly read localStorage and steal the token.

HttpOnly Cookies solve this because the browser explicitly prevents JavaScript from reading the cookie. However, cookies automatically attach to every request, opening the app to CSRF (Cross-Site Request Forgery). An attacker can trick the user into clicking a link, and the browser will blindly attach the authentication cookie to the forged request. Architects solve this by using HttpOnly cookies paired with strict SameSite=Strict attributes and anti-CSRF header tokens.

Q93
How do you ensure React applications comply with strict Content Security Policy (CSP) headers?

A strict CSP prevents XSS by forbidding the browser from executing inline scripts (<script>alert(1)</script>) or inline styles.

React Server Components and SSR frameworks (like Next.js) often inject inline scripts to pass the hydration payload or manage styling (e.g., Styled Components). To comply with CSP, an architect must configure the server to generate a cryptographically secure random Nonce on every request. This nonce must be attached to the CSP HTTP header, and then passed into the React application so React can attach `nonce=”random_value”` to the inline script/style tags it generates.

Q94
What is DOM Clobbering, and how can it break a React application’s state management?

DOM Clobbering is a legacy browser quirk where HTML elements with an id or name attribute are automatically mapped to properties on the global window object.

Real-World Scenario: A React app relies on window.location for routing. An attacker injects <a id="location" href="malicious.com"> into a comment section. Because of DOM clobbering, `window.location` is overwritten. It no longer points to the native browser API; it points to the HTML anchor tag. When the React router attempts to read `window.location.pathname`, the app crashes or behaves unpredictably. Architects prevent this by avoiding global `window` reliance and strictly sanitizing user HTML.
Q95
How do you implement robust focus trapping for complex, nested modals to meet WCAG security and accessibility standards?

If a modal opens and a user presses “Tab”, the focus will eventually leave the modal and highlight elements in the background UI. This is highly disorienting for screen readers and technically allows interaction with an “inactive” background state.

Architects implement a Focus Trap. You listen to the `keydown` event on the modal. If the user presses “Tab” while focused on the last focusable element in the modal, you execute `e.preventDefault()` and programmatically call `.focus()` on the first element in the modal, creating an infinite loop. Crucially, when the modal closes, you must restore focus to the exact button that originally opened the modal.

Q96
Explain the ARIA live region pattern for announcing dynamic content changes in React SPAs.

In a traditional website, clicking “Submit” reloads the page, and the screen reader reads the new page title (“Success!”). In a React SPA, clicking submit might just render a tiny green checkmark dynamically. A blind user will hear absolutely nothing and assume the form is broken.

Architects use aria-live regions. You place an invisible <div aria-live="polite" aria-atomic="true"> permanently in the DOM. When the React async action succeeds, you update the text inside this div. The browser detects the DOM mutation and instructs the screen reader to interrupt its flow and audibly announce the new text (“Form submitted successfully”) to the user.

Q97
How do you handle proper keyboard focus management during client-side route transitions?

When React Router swaps components, the DOM changes completely, but the browser’s focus often remains on the `` element or gets lost in the void. A keyboard user has to manually tab all the way through the navigation bar again to reach the new page content.

Architects enforce a global routing effect. On every route change, React must programmatically call .focus() on the top-level <h1> of the new page (which requires adding tabIndex="-1" to the H1 so it can receive programmatic focus). This instantly drops the screen reader or keyboard user precisely at the beginning of the new content.

Q98
Discuss the implications of the target="_blank" attribute and the necessity of rel="noopener noreferrer".

Historically, opening a link in a new tab via target="_blank" gave the newly opened page access to the window.opener object of the original page. This created a massive security vulnerability: the new page could maliciously execute window.opener.location = 'phishing-site.com', hijacking the user’s original tab without their knowledge.

React heavily warned developers to append rel="noopener noreferrer" to sever this connection. While modern browsers (Chrome 88+) now implicitly enforce `noopener` on `target=”_blank”`, architects still explicitly require it in React codebases to support legacy enterprise browsers and Safari versions.

Q99
How do you handle third-party scripts (like ads or analytics) that mutate the DOM inside a React component’s purview?

React assumes total authority over its Virtual DOM tree. If an external library (like Google Maps, an Ad network, or jQuery) appends or deletes a child node inside a React-managed <div>, React will eventually attempt to update that node. Finding the expected DOM structure broken, React throws a fatal NotFoundError (or Invariant Violation) and crashes.

Architects isolate external mutations. You render a completely empty <div ref={containerRef} />. You use a useEffect to pass that DOM node to the third-party library, and you never pass React children into that div. Furthermore, you can use shouldComponentUpdate (or memoization) returning `false` to guarantee React never attempts to reconcile that specific subtree again.

Q100
What is Prototype Pollution, and how can it catastrophically affect a React application’s state management layer?

Prototype Pollution occurs when developers use unsafe recursive merge functions (like deep-merging user payload data into Redux state). If an attacker sends a JSON payload with a key named __proto__, a naive merge function will traverse up to the global JavaScript Object.prototype and inject properties directly into it.

Real-World Scenario: An attacker sends `{ “__proto__”: { “isAdmin”: true } }`. The unsafe deep-merge pollutes the global prototype. Suddenly, every single object in the React application (including empty objects `{}`) inherits `isAdmin: true`. When your React routing logic checks `if (userState.isAdmin)`, it resolves to `true`, granting the attacker total unauthorized access to the application. Architects prevent this by using secure libraries (like Lodash’s `merge` which actively drops `__proto__` keys) or freezing prototypes.
// Unsafe merge function causing Prototype Pollution
function unsafeMerge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object') {
      if (!target[key]) target[key] = {};
      // DANGER: If key is "__proto__", this alters the global Object prototype!
      unsafeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

React JS Interview question for Intermediate level

Elevate your skills with 100 intermediate-level React questions. Dive deep into Custom Hooks, Performance Optimization, Redux, Next.js SSR, and React 19 Concurrent patterns.

1. Advanced Hooks & Custom Hooks

Q1
How does useLayoutEffect differ from useEffect, and when would you actually use it?
The Core Concept: Both handle side effects, but their timing differs. useEffect runs asynchronously *after* the browser paints the screen. useLayoutEffect runs synchronously *before* the browser paints.

The Why & How: React calculates the DOM mutations, runs useLayoutEffect, and only then hands the final DOM to the browser to paint. Because it blocks the visual update, it should be used sparingly to avoid performance bottlenecks.

Real-World Scenario: You are building a custom tooltip that needs to calculate its position based on the dimensions of a target button. If you use useEffect, the user might see the tooltip render at the top left of the screen for a split second before it “jumps” to the correct spot (a flicker). useLayoutEffect measures the DOM and moves the tooltip before the user ever sees it.
Q2
Can you explain the “stale closure” problem in React hooks and how to solve it?
The Core Concept: A stale closure happens when a function inside a component (often an event handler, a timeout, or inside a useEffect) references an old state or prop variable because it captured the variable at the time it was created, not its current value.

The Why & How: This usually occurs when you omit variables from a hook’s dependency array. To fix it, ensure all dependencies are listed, or use a functional state update (e.g., setCount(prev => prev + 1)) so the state setter doesn’t need to capture the current state from the outer scope.

Real-World Scenario: You are building a strict 30-minute countdown timer for a hyper-local quick-commerce app. You set up a setInterval inside a useEffect with an empty dependency array. If you try to do setTime(time - 1), the timer will drop by one second and freeze permanently because time is trapped at its initial value in that closure. Using setTime(prevTime => prevTime - 1) safely bypasses the stale closure.
Q3
When should you reach for useReducer instead of useState?
The Core Concept: useReducer is preferable when state logic is complex, involves multiple sub-values, or when the next state depends heavily on the previous state.

The Why & How: It centralizes all state transitions into a single, predictable reducer function. This decouples the “how state updates” logic from the component rendering, making it much easier to write unit tests for the state logic independent of the UI.

Real-World Scenario: Managing a complex checkout form for a dark-store delivery app. Instead of having setLoading, setError, setUserData, and setCartTotal scattered across multiple try/catch blocks, you dispatch a single action: dispatch({ type: 'CHECKOUT_SUCCESS', payload: data }). The reducer cleanly updates all four of those states in one centralized block.
Q4
What is lazy initialization in useState, and what specific problem does it solve?
The Core Concept: Passing a function to useState (e.g., useState(() => computeHeavyValue())) instead of a direct value.

The Why & How: During a re-render, React ignores the initial value passed to useState. However, if that value is a heavy computation, JavaScript still executes the computation before React discards it. Lazy initialization ensures the function is *only* executed on the very first mount.

Real-World Scenario: You need to initialize state by formatting a massive array of AI-generated astrology and numerology predictions. Doing formatPredictions(hugeArray) directly inside useState causes a heavy synchronous computation on *every single keystroke* or re-render of that component. Lazy initialization prevents this CPU bottleneck.
Q5
What is useSyncExternalStore and why was it introduced in React 18?
The Core Concept: It’s a hook designed to safely subscribe a React component to external data sources that exist outside of React’s state tree.

The Why & How: With the introduction of Concurrent Mode in React 18, React can pause and resume rendering. If an external store updates *while* React is in the middle of a paused render, the UI could render with two different values for the same state (known as “tearing”). This hook forces synchronous updates to prevent tearing.

Real-World Scenario: Subscribing directly to a browser API, like window.matchMedia for responsive design, or reading a user’s network status (navigator.onLine) for an offline-first delivery rider app. It’s also heavily used under the hood by state libraries like Redux and Zustand.
Q6
How do useCallback and useMemo impact performance, and what are their anti-patterns?
The Core Concept: useMemo caches the result of a calculation. useCallback caches a function definition. They prevent unnecessary re-creations of references on every render.

The Why & How: The anti-pattern is using them everywhere. Memoization has a cost (memory allocation and dependency array comparison). If the calculation is trivial, the overhead of useMemo is worse than just recalculating it.

Real-World Scenario: You have a heavily memoized <ProductTable> component that receives an onDelete prop. If you don’t wrap the parent’s handleDelete function in a useCallback, a new function reference is created every time the parent renders, breaking the memoization of the child table and causing the entire table of 500 items to re-render unnecessarily.
Q7
What is the architectural purpose of useImperativeHandle?
The Core Concept: It allows a child component to customize the methods or properties it exposes to a parent component via a ref.

The Why & How: Usually, passing a ref down (via forwardRef) gives the parent full access to the underlying DOM node (like an <input>). useImperativeHandle intercepts this, allowing you to hide the actual DOM node and only expose specific, controlled functions.

Real-World Scenario: You build a complex, custom <BottomSheet> component for a mobile-first app. The parent shouldn’t care about the internal DOM structure of the sheet. Using this hook, you can expose just two clean methods to the parent: sheetRef.current.open() and sheetRef.current.close().
Q8
Why shouldn’t you mutate refs (ref.current) directly during the render phase?
The Core Concept: The render phase must be “pure.” Mutating a ref during render violates React’s functional principles.

The Why & How: In Concurrent React, a component might be rendered multiple times before those changes are actually committed to the screen (or the render might be aborted entirely). If you mutate a ref during render, you permanently alter state in a way that React cannot track or roll back.

Real-World Scenario: If you push an event to an analytics array via ref.current.push(event) right inside the component body, Concurrent mode might run that code three times while preparing the UI, resulting in three duplicate analytics tracking events firing for a single page view.
Q9
How do you handle complex race conditions in useEffect when fetching data?
The Core Concept: A race condition occurs when subsequent asynchronous requests resolve out of order, leading the UI to display stale or incorrect data.

The Why & How: If Request A is fired, then immediately Request B is fired, but Request B’s API is faster, Request B loads, and then Request A’s delayed response overwrites it. To fix this, you must invalidate previous requests when the effect re-runs.

Real-World Scenario: A search bar with autocomplete. A user types “A”, then “App”, then “Apple”. You fire three API requests. To prevent the “A” result from arriving last and overwriting “Apple”, you instantiate an AbortController inside the useEffect. In the cleanup function, you call controller.abort(). This instantly cancels any pending, outdated network requests.
Q10
Explain the difference between Component State, Context State, and Global Server State. How do you decide which to use?
The Core Concept: State should live as close to where it’s needed as possible and be categorized by its origin.

The Why & How:
* Component State (useState): Ephemeral UI state (is a modal open, text input values).
* Context/Global State (Zustand/Redux): State shared across wide, unrelated areas of the app (UI themes, user auth status, complex client-side multi-step wizards).
* Server State (React Query/SWR): Asynchronous data that originates from a backend database.

Real-World Scenario: In a delivery app, the typed delivery instructions go in Component State. The user’s JWT auth token goes in Context/Global State. The list of available restaurants is Server State handled by a data-fetching library that manages caching and background refetching.

2. Context API & State Management

Q11
What is the primary performance pitfall of the Context API, and why does it happen?
The Core Concept: Whenever a Context Provider’s value changes, *every single component* consuming that context will be forced to re-render.

The Why & How: React Context does not have a native mechanism to subscribe to a *slice* of the context object. If your context holds { theme: 'dark', user: { name: 'Alex' } }, updating the theme will forcefully re-render the <ProfileHeader /> component, even if it only cares about the user object.

Real-World Scenario: Placing your entire application state inside a single monolithic <AppStateContext>. If a user types into a deeply nested search input tied to that context, the entire component tree updates on every single keystroke, causing massive UI lag.
Q12
How can you architect Context to avoid the re-rendering pitfall?
The Core Concept: Context splitting and separating state from dispatch.

The Why & How: Create logically isolated contexts based on domain (e.g., AuthContext, ThemeContext). Furthermore, separate the state from the functions that update it. Create a UserValueContext and a UserDispatchContext.

Real-World Scenario: You have a “Logout” button deep in a nested settings menu. By wrapping it in UserDispatchContext, it only receives the logout() function. When the user’s data changes, the button component does not re-render because it is entirely decoupled from the UserValueContext.
Q13
When would you definitively choose Redux over the Context API?
The Core Concept: Context is primarily for dependency injection (avoiding prop drilling). Redux is a predictable state container architecture.

The Why & How: Reach for Redux (or Zustand) when you have high-frequency state updates, complex state transition logic that benefits from middleware, or when you need granular selectors (e.g., useSelector(state => state.cart.total)) to prevent unnecessary re-renders natively.

Real-World Scenario: Building a dashboard for a quick-commerce dark store with real-time inventory updates streaming in via WebSockets. Context would buckle under the rapid updates, causing continuous full-tree renders. Redux allows specific table rows to subscribe directly to specific slices of the inventory data stream without impacting the rest of the UI.
Q14
What is Redux Toolkit (RTK), and why is it now the industry standard over legacy Redux?
The Core Concept: RTK is the official, opinionated set of tools for writing Redux logic.

The Why & How: Legacy Redux required massive boilerplate: separate actions, action creators, constants, and complex immutable reducers. RTK uses “slices” to auto-generate actions and action creators. Most importantly, it integrates Immer.js under the hood.

Real-World Scenario: In legacy Redux, updating a nested user address required agonizing spread operators: return { ...state, user: { ...state.user, address: { ...state.user.address, city: 'Delhi' } } }. With RTK, you just write state.user.address.city = 'Delhi'. RTK safely translates this mutable syntax into immutable updates behind the scenes.
Q15
How does Redux Toolkit’s RTK Query (or React Query) solve the problems of traditional data fetching?
The Core Concept: They shift the paradigm from “fetching data and putting it into global UI state” to “treating the server as the source of truth and managing an intelligent cache locally.”

The Why & How: Traditionally, developers fetched data in useEffect and dispatched it to a Redux store. This meant writing loading, success, and error states manually. Server-state libraries handle caching, deduplication of duplicate requests, background polling, and cache invalidation automatically.

Real-World Scenario: You have a ProductList and a SidebarSummary that both need the same /api/products data. With React Query, both components can call useQuery('products'). The library is smart enough to only send *one* network request to the backend, sharing the cached response with both components instantly.
Q16
What makes Zustand different from Redux, and why is it gaining popularity?
The Core Concept: Zustand is a minimalist, hook-based state manager that operates without wrapping your app in Context Providers.

The Why & How: Because the store sits entirely outside the React component tree, it circumvents the React Context rendering rules. It has virtually no boilerplate compared to Redux and allows components to subscribe to partial state easily using selectors.

Real-World Scenario: You need a global toggle for a side navigation drawer. Bootstrapping Redux for a simple boolean is overkill. Zustand allows you to create a global store in 5 lines of code, and access it anywhere using a simple hook, keeping the codebase lean and highly performant.
Q17
Explain the concept of “Atomic” state management (like Recoil or Jotai).
The Core Concept: Instead of a single monolithic object store (Redux), state is broken down into independent, derived pieces called “atoms”.

The Why & How: Atoms can be dynamically created and linked together. Components subscribe only to the specific atoms they need. When an atom updates, only the components subscribed to that specific atom re-render.

Real-World Scenario: You are building an interactive canvas application (like Figma or Excalidraw). Each shape on the canvas has its own X/Y coordinates. Storing 10,000 shapes in one Redux object means moving *one* shape recalculates the whole state tree. Making each shape an independent Recoil atom allows you to move one shape at 60fps without touching the others.
Q18
Is Prop Drilling inherently an anti-pattern?
The Core Concept: No, prop drilling is the most direct, explicit, and traceable way to share state in React.

The Why & How: It only becomes an anti-pattern when props are passed through many layers of intermediary components that do not need the data, creating a brittle and hard-to-refactor codebase.

Real-World Scenario: Passing a userId from a <Dashboard> down to a <UserProfile> and then to a <UserAvatar> is perfectly fine. But if you have to pass it through 10 generic layout wrappers that don’t care about the user, you should use Context or Component Composition (passing the Avatar as a children prop) instead.
Q19
Can you consume a React Context outside of a React component (e.g., inside an Axios interceptor utility file)?
The Core Concept: No. Context relies strictly on React’s component tree and hook architecture.

The Why & How: Hooks like useContext require a React render cycle to function. To share data with non-React files, you must use an external state manager (like Redux or Zustand stores, which expose getState() outside the tree) or pass the context value as an explicit argument to the utility function when called from within a component.

Real-World Scenario: You need to attach a JWT token to every outgoing Axios request. You cannot call useAuthContext() inside api.js. Instead, you create a Zustand store, and in your api.js file, you call useAuthStore.getState().token to retrieve the token outside of the React lifecycle.
Q20
How do you persist global state across page reloads in React?
The Core Concept: React state lives in memory and is destroyed on refresh. To persist it, you must synchronize your state with browser storage (localStorage, sessionStorage, or IndexedDB).

The Why & How: Writing custom useEffect hooks to read/write to storage is error-prone and can cause hydration mismatches in SSR. It is best to use built-in middlewares like Redux Persist or Zustand’s persist middleware. These handle serializing state to storage on change and hydrating it on initial load automatically.

Real-World Scenario: A user adds items to a shopping cart for a 30-minute delivery window but accidentally refreshes the page. By using Zustand’s persist middleware wrapped around the cart store, the state is automatically saved to localStorage. On refresh, the middleware rehydrates the cart instantly before the first render, preventing data loss.

3. Performance Optimization

Q21
How does React.memo determine if a component should re-render?
The Core Concept: By default, React.memo performs a shallow comparison of all current and next props using Object.is().

The Why & How: It checks if the memory references of the props have changed. If the parent renders but passes down the exact same prop references, the memoized child skips its render phase and reuses its last DOM output.

Real-World Scenario: You have a heavy <DataGridRow> component in a financial dashboard. If the specific row’s data hasn’t changed, React.memo stops the row from re-rendering when the global market ticker updates the parent component’s state.
Q22
How do you implement a custom comparison function in React.memo?
The Core Concept: You can pass a comparison function as the second argument: React.memo(Component, arePropsEqual).

The Why & How: This function takes prevProps and nextProps. You return true if they are functionally equivalent (which skips the render) and false if they differ (which triggers the render). This overrides the default shallow comparison.

Real-World Scenario: A <UserProfileCard> takes a massive user object prop, but only physically displays the avatarUrl. You write a custom comparison: (prev, next) => prev.user.avatarUrl === next.user.avatarUrl. Now, if the user’s lastLoginTime updates in the background, the card won’t waste cycles re-rendering.
Q23
Why does passing an inline object or function immediately break React.memo?
The Core Concept: Inline objects and functions create brand new memory references every time the parent component executes its render function.

The Why & How: Because React.memo uses shallow comparison, it sees that {} !== {}. It interprets the inline object as a completely “new” prop and forcefully re-renders the memoized component anyway, defeating the purpose of the memoization.

Real-World Scenario: Passing style={{ marginTop: '10px' }} directly to a memoized <Button>. On every parent render, a new style object is minted. To fix this, you must extract the object outside the component or wrap it in useMemo.
Q24
When is using useMemo actually counterproductive to performance?
The Core Concept: Memoization is not free; it has an upfront CPU and memory cost to allocate the cache and constantly check dependency arrays.

The Why & How: If a computation is incredibly fast (like basic string concatenation or mapping a 10-item array), the engine overhead of running useMemo is actually slower and uses more memory than just recalculating the value on every render.

Real-World Scenario: Doing useMemo(() => users.map(u => u.name), [users]) on an array of 5 active users. The optimization overhead is worse than the task. Reserve useMemo for sorting arrays of 5,000+ items or parsing complex datasets.
Q25
What is Windowing (or Virtualization) in React architecture?
The Core Concept: It is a UI technique that only renders the exact DOM nodes currently visible in the user’s browser viewport.

The Why & How: Instead of the browser natively scrolling thousands of DOM elements, virtualization libraries (like react-window) calculate the math of the scroll position, absolutely position the visible items, and recycle DOM nodes as they scroll out of view.

Real-World Scenario: Building a Slack-like chat application where a channel might have 100,000 messages in history. Rendering 100k DOM nodes will crash a mobile browser. Virtualization keeps exactly 20 message DOM nodes on screen at any given time, maintaining flawless 60fps scrolling.
Q26
What is the React Profiler API and how is it used to debug performance?
The Core Concept: It is a programmatic API (and DevTools tab) designed to measure rendering performance and identify bottlenecks in a React tree.

The Why & How: You wrap parts of your app in <Profiler id="Name" onRender={callback}>. React calls the callback with data detailing the “commit” time, helping you identify which components render too often or take too long.

Real-World Scenario: Users report the “Checkout” page is lagging on older phones. You wrap the form in a Profiler and discover that typing in the “Discount Code” field is causing a heavy <ShippingMap> component to take 150ms to re-render on every keystroke. You then isolate the input state to fix the lag.
Q27
How do you identify and fix memory leaks in React functional components?
The Core Concept: Memory leaks are typically caused by lingering asynchronous tasks, intervals, or event listeners that keep referencing a component after it has been removed from the DOM.

The Why & How: Often spotted by a rising heap size in Chrome DevTools or the classic “Can’t perform a React state update on an unmounted component” warning. You fix them by returning a strict cleanup function inside your useEffect.

Real-World Scenario: A <StockTicker> sets up a setInterval to fetch prices every 5 seconds. The user navigates to a new page, destroying the component, but the interval keeps running in the background, consuming CPU and trying to call setPrice. Returning () => clearInterval(id) in the effect guarantees the leak is plugged.
Q28
How does React.lazy combined with Suspense improve application performance?
The Core Concept: It enables component-level code splitting, meaning JavaScript is only downloaded when it’s strictly needed.

The Why & How: React.lazy dynamically imports a component only when it actually mounts. <Suspense> wraps it to show a fallback UI (like a spinner) while the browser fetches that specific JavaScript chunk over the network, drastically shrinking the initial bundle size.

Real-World Scenario: An e-commerce app has a massive 3D <ProductCustomizer> powered by Three.js. You don’t want the 90% of users who are just browsing to download that 2MB JavaScript payload. React.lazy ensures the 3D engine is only fetched over the network if a user explicitly clicks the “Customize in 3D” button.
Q29
Architecturally, what is the difference between the “Render Phase” and “Commit Phase”?
The Core Concept: These are the two distinct steps React takes to update the UI.

The Why & How: The Render Phase is where React calls your component functions to calculate changes (Virtual DOM diffing). This phase is pure and, in Concurrent Mode, can be paused or aborted. The Commit Phase is where React synchronously applies those calculated changes to the actual browser DOM (mutating nodes). This cannot be interrupted.

Real-World Scenario: You trigger an urgent state update (typing) while a slow background list is filtering. React interrupts the list’s Render Phase, completely processes your keystroke through its own Render and Commit phases, and then calmly restarts the list’s Render Phase in the background.
Q30
How does the concept of “Decoupling State” fundamentally minimize re-renders?
The Core Concept: Pushing state down the component tree ensures that fewer components are affected by a state change.

The Why & How: If state lives at the top of a tree, any change forces the entire tree below it to re-render. Moving state exclusively into the child component that needs it physically isolates the render boundary.

Real-World Scenario: You have a <GlobalLayout> containing a massive sidebar and main content. Putting an isHovered state for a single sidebar button at the layout level re-renders the whole page whenever the user moves their mouse. Moving that useState explicitly inside the individual <SidebarButton> component makes the hover effect perfectly isolated.

4. Component Patterns & Architecture

Q31
What is a Higher-Order Component (HOC) and when is it still architecturally relevant?
The Core Concept: An HOC is a function that takes a component as an argument and returns a new, enhanced component with injected logic or props.

The Why & How: While custom hooks have largely replaced HOCs for logic sharing, HOCs are still superior for cross-cutting structural concerns that completely wrap or conditionally block UI rendering.

Real-World Scenario: You have 15 different dashboard pages. Instead of putting auth-checking hooks in every single file, you wrap them: export default withRoleGating(FinanceDashboard, ['ADMIN']). The HOC automatically intercepts the render, checking permissions, and returning a 403 redirect if they fail, keeping the Dashboard component perfectly clean.
Q32
Explain the Render Props pattern and its modern alternatives.
The Core Concept: Passing a function that returns React elements as a prop (usually the children prop), allowing the parent to manage state and the child to dictate rendering.

The Why & How: It solves the same logic-sharing problem as hooks but operates entirely within the JSX tree. While custom hooks are cleaner for raw data, Render Props are excellent when complex internal logic is heavily coupled to highly customizable UI layouts.

Real-World Scenario: Building a <VirtualList>. The parent component calculates the complex scroll math and visible indices, but it leaves the visual UI completely up to the consumer: <VirtualList data={items}>{ (item) => <MyCustomRow data={item} /> }</VirtualList>.
Q33
What is the Compound Components pattern?
The Core Concept: Multiple distinct components working together to form a cohesive UI widget, sharing implicit state behind the scenes (usually via Context).

The Why & How: It provides a highly flexible, declarative API. The parent component manages the state, and child components read it, freeing developers from passing a dozen messy config props to a single monolithic component.

Real-World Scenario: A generic <Tabs> component. Instead of writing <Tabs config={[{title: 'A', content: '...'}]} />, you architect it as: <Tabs> <Tabs.List> <Tabs.Tab>A</Tabs.Tab> </Tabs.List> </Tabs>. This allows consumers to inject custom icons or rearrange layouts without touching the internal state logic.
Q34
What is the Control Props pattern?
The Core Concept: Designing a component so it can either manage its own internal state automatically, OR be fully controlled by a parent component via props.

The Why & How: You initialize internal state, but on every render, you check if the parent passed a specific controlled prop. If the prop exists, it overrides the internal state, giving the consumer maximum flexibility.

Real-World Scenario: Building a reusable <Accordion> for a design system. Team A just drops it in and lets it handle its own open/close clicks. Team B wants to sync the Accordion with a URL hash so users can bookmark it. Team B passes <Accordion isOpen={urlHash === 'details'} onChange={updateUrl} />, cleanly hijacking the internal state.
Q35
Are Container vs Presentational components still relevant in the era of Hooks?
The Core Concept: The strict physical file separation is dead, but the architectural principle of separating concerns lives on.

The Why & How: Hooks allowed us to easily co-locate data fetching inside UI components. However, tightly coupling complex API fetching directly inside complex CSS grid layouts makes the UI nearly impossible to unit test or view in isolation.

Real-World Scenario: You are writing a <UserProfile>. Instead of putting Axios calls right inside the complex layout, you create a <UserProfileContainer> that calls useQuery, handles loading states, and passes the raw data to a pure, “dumb” <UserProfileView>, making the view easily renderable in Storybook.
Q36
What are React Portals and what structural UI problems do they solve?
The Core Concept: ReactDOM.createPortal(child, domNode) allows you to render a component’s HTML into a completely different part of the actual browser DOM tree.

The Why & How: It solves critical CSS stacking context (z-index) and overflow: hidden issues while keeping the component logically attached to the React tree (so context and events still bubble up normally).

Real-World Scenario: A “Delete Account” confirmation Modal is triggered deep inside a <SettingsCard> that has overflow: hidden styling. Without a portal, the modal gets cut off by the card’s boundaries. Portaling the modal directly to document.body ensures it renders cleanly as an overlay across the entire application.
Q37
What is the “Slots” pattern in React?
The Core Concept: Passing React elements into named props rather than dumping everything into a single children prop.

The Why & How: It is highly effective for strict layout components where you need specific content injected into designated, non-sequential template areas.

Real-World Scenario: An enterprise <DashboardLayout> needs a sidebar, a header, and main content. Instead of confusing children logic and mapping, you design it with slots: <DashboardLayout leftSidebar={<AdminNav />} header={<AppHeader />}> <MainChart /> </DashboardLayout>.
Q38
How do you safely architect a Recursive Component?
The Core Concept: A component that renders instances of itself inside its own render function to handle deeply nested, unpredictable data structures.

The Why & How: Just like a recursive mathematical function, you MUST define a base case (a stop condition) to prevent infinite loops, usually by checking if a children array is empty or undefined before rendering the next layer.

Real-World Scenario: Building a VS Code style file explorer sidebar. You build a <FileTree node={folderData} />. If folderData has a children array, the component maps over it and returns <FileTree node={child} /> for each one, dynamically drilling down until it hits a simple file node.
Q39
What is a Polymorphic Component?
The Core Concept: A component that can render as different underlying HTML tags based on a prop (usually as or component), while maintaining its unified styling and behavior.

The Why & How: Built using a dynamic tag element (e.g., const Component = as || 'div'). This pattern is absolutely crucial for maintaining semantic HTML and accessibility in design systems.

Real-World Scenario: A standardized <Button> component. Sometimes it submits a form, sometimes it navigates routes. Using <Button as="a" href="/home">Home</Button> renders a proper anchor tag for SEO and screen readers, but applies the exact visual CSS of the standard application button.
Q40
Why is defining components inside other components an architectural disaster?
The Core Concept: It causes complete remounting instead of standard re-rendering.

The Why & How: Every time the parent component renders, a brand new function (and therefore a brand new component reference) is created in memory. React sees it as a completely different component type. It unmounts the old DOM, destroys all local state, and heavily hits the CPU to mount a new one.

Real-World Scenario: You casually define const UserRow = () => <tr>...</tr> inside a <Table> component. When the table sorts, the parent renders. Instantly, all inputs inside UserRow lose focus, and all local state (like a checked “select” box) is permanently wiped out because the DOM nodes were entirely destroyed and rebuilt from scratch.

5. React Router & Navigation

Q41
What is the difference between useNavigate and useLocation?
The Core Concept: useNavigate triggers navigation actions, while useLocation reads the current routing state.

The Why & How: useNavigate returns a function allowing you to programmatically mutate the browser’s history stack (pushing or replacing routes). useLocation returns an object representing the current URL, which is often used inside `useEffect` dependency arrays to trigger logic when the route changes.

Real-World Scenario: You use navigate('/success') to redirect a user after a successful Stripe payment completes. You use useLocation() to track page views in Google Analytics every time the location.pathname updates.
Q42
Architecturally, how do URL Parameters differ from Query Parameters?
The Core Concept: URL Parameters identify a specific resource, while Query Parameters modify how that resource is viewed.

The Why & How: URL Parameters are dynamic path segments defined in your route config (e.g., /users/:id) and accessed via useParams(). Query Parameters are key-value pairs appended after a question mark (e.g., ?sort=asc) and accessed via useSearchParams().

Real-World Scenario: In an e-commerce app, /products/9876 (URL Param) tells the app *which* exact shoe to fetch from the database. /products?color=red&size=10 (Query Params) tells the app to fetch a list of products and *filter* them.
Q43
How do you properly implement Protected Routes?
The Core Concept: Protected routes act as routing middleware, intercepting navigation to check authentication state before rendering the destination.

The Why & How: You create a wrapper component (e.g., <RequireAuth>) that checks the user context. If authenticated, it renders <Outlet />. If not, it returns a <Navigate to="/login" replace /> component to bounce them away without leaving a broken history entry.

Real-World Scenario: A user tries to access /admin/billing while logged out. The <RequireAuth> wrapper intercepts them, saves /admin/billing in the routing state, and redirects them to /login. After logging in, the auth flow reads the saved state and seamlessly redirects them back to their intended destination.
Q44
What is the specific architectural purpose of the <Outlet /> component?
The Core Concept: <Outlet /> is a placeholder inside a parent route layout where matched child routes are rendered.

The Why & How: It allows you to build complex, persistent UI shells (like headers and sidebars) that do not unmount or re-render when a user navigates between sub-pages, vastly improving performance and state retention.

Real-World Scenario: You have a <DashboardLayout> with a heavy, collapsible sidebar. Inside the layout, you put an <Outlet />. When the user clicks from “Settings” to “Profile”, only the component inside the Outlet is swapped out. The sidebar’s open/close state is perfectly preserved because the parent layout never unmounted.
Q45
What are React Router Data Loaders (introduced in v6.4)?
The Core Concept: They shift the data fetching paradigm from “Fetch-on-Render” to “Fetch-before-Render.”

The Why & How: Traditionally, a component mounts, shows a spinner, runs a useEffect, and then shows data (a waterfall). `loader` functions execute in parallel with the routing transition. React Router pauses the UI, fetches the data, and only renders the new page once the data is fully ready.

Real-World Scenario: Clicking a link to a blog post. With loaders, the URL changes, but the page doesn’t tear down immediately to show a blank loading screen. The router fetches the post in the background, and the UI snaps instantly to the fully populated blog post.
Q46
What are React Router Actions, and how do they tie into Loaders?
The Core Concept: `action` functions handle data mutations natively via HTML forms, completely eliminating the need for manual `onSubmit` handlers and `fetch` calls.

The Why & How: When a user submits a `
`, React Router intercepts it and passes the form data to your route’s `action`. The magical part: once the action completes, React Router *automatically* re-runs the `loader` for that page to ensure the UI data is perfectly synced with the server.

Real-World Scenario: Submitting an “Add New User” form. The action sends the POST request. You don’t need to write code to manually refetch the user table; the router automatically triggers the table’s loader, updating the UI instantly.
Q47
How do you create a Catch-all (404) route?
The Core Concept: A fallback route that matches any URL path not explicitly defined in your routing tree.

The Why & How: You define a route with a splat wildcard path: <Route path="*" element={<NotFound />} />. It must be placed at the very bottom of your routing configuration so it only triggers if all other specific route matches fail.

Real-World Scenario: A user gets a link in an email but accidentally deletes the last letter (/campaign/sale-202 instead of 2024). Instead of a blank white screen, the splat route catches them and renders a branded 404 page featuring a search bar and a “Return Home” button to prevent user drop-off.
Q48
When is it appropriate to use programmatic navigation over declarative navigation?
The Core Concept: Declarative navigation is user-driven. Programmatic navigation is code-driven.

The Why & How: You should *always* default to declarative <Link> components for navigation because they generate valid HTML <a> tags, which are crucial for screen readers, SEO, and allowing users to “right-click -> open in new tab”. Programmatic navigate() should be strictly reserved for side-effects of business logic.

Real-World Scenario: An architect will reject a PR that uses onClick={() => navigate('/about')} on a `div` styled as a button. navigate() should only be used inside a .then() block after an API call, like successfully submitting a multi-step wizard and pushing the user to a dashboard.
Q49
What is the difference between Link and NavLink?
The Core Concept: <NavLink> is a specialized version of <Link> that has implicit awareness of the current URL state.

The Why & How: NavLink automatically provides an isActive boolean to its `className` or `style` props if its `to` path matches the browser’s current URL. This eliminates the need for you to manually compare location.pathname against strings.

Real-World Scenario: Building a site header. Using <NavLink to="/dashboard"> allows you to trivially apply a bold font and blue underline to the “Dashboard” menu item exclusively when the user is actually viewing the dashboard.
Q50
Why use BrowserRouter over HashRouter?
The Core Concept: BrowserRouter relies on the server; HashRouter relies entirely on the client.

The Why & How: BrowserRouter uses the modern HTML5 History API for clean URLs (/about), but requires a backend configured to redirect all requests to `index.html`. HashRouter injects a hash (/#/about), ensuring the server ignores everything after the hash, treating it as a single page request.

Real-World Scenario: Use BrowserRouter for all standard production deployments (Vercel, AWS, Netlify). You only fallback to HashRouter when deploying to environments where you cannot configure server rewrite rules, like a raw S3 bucket without CloudFront, or packaging the app via Electron.

6. Error Handling & Boundaries

Q51
What is an Error Boundary in React?
The Core Concept: An Error Boundary is a defensive React component that catches JavaScript errors anywhere in its child component tree, logs them, and displays a fallback UI.

The Why & How: Before boundaries, a single syntax error in a deeply nested component would unmount the entire React tree, leaving the user with a blank white screen. Boundaries act like a massive `try/catch` block for declarative rendering.

Real-World Scenario: A third-party mapping widget receives a malformed JSON payload and throws a `TypeError` while rendering. The Error Boundary catches it, replacing the broken map with a “Map Unavailable” graphic, while keeping the rest of the dashboard (nav bars, metrics) perfectly functional.
Q52
Why must Error Boundaries be written as Class Components?
The Core Concept: There are currently no Hook equivalents for the specific error-catching lifecycle methods.

The Why & How: To build a boundary from scratch, you must use static getDerivedStateFromError and componentDidCatch, which only exist on `React.Component` classes.

Real-World Scenario: While you must use a class under the hood, modern architectures rarely write them from scratch anymore. Teams rely on standardized wrappers like the react-error-boundary library so they can stay entirely within functional paradigm workflows.
Q53
What errors do Error Boundaries NOT catch?
The Core Concept: They are strictly limited to the React rendering lifecycle.

The Why & How: Boundaries only catch errors thrown during the render phase, inside lifecycle methods, and in constructors. They do *not* catch errors inside event handlers (like button clicks), asynchronous code (like `setTimeout` or `fetch`), server-side rendering, or errors thrown within the boundary component itself.

Real-World Scenario: If a user clicks “Submit” and your onClick handler tries to read user.address.zipcode when address is null, the handler will throw an error. The Error Boundary will completely ignore this. The UI won’t crash, but the button will silently fail to do anything.
Q54
How do you handle errors inside event handlers?
The Core Concept: Standard JavaScript control flow.

The Why & How: Because Error Boundaries ignore event handlers, you must proactively wrap risky logic in try/catch blocks and manually update component state to reflect the failure.

Real-World Scenario: Inside a checkout button’s handlePayment async function, you wrap the API call in `try/catch`. In the `catch` block, you call setPaymentError(err.message), which triggers the component to conditionally render a red alert box above the form.
Q55
What is the specific role of static getDerivedStateFromError?
The Core Concept: State recovery and fallback rendering.

The Why & How: It is invoked synchronously after an error is thrown in a descendant. It receives the error object and its sole purpose is to return a new state object (e.g., { hasError: true }). This state update forces the boundary to re-render, displaying the fallback UI instead of the crashed children.

Real-World Scenario: As soon as the child tree panics, this static method intercepts the crash, flips the internal `hasError` switch, and the component’s `render()` method immediately returns the “Oops, something went wrong” component to the screen.
Q56
What is the specific role of componentDidCatch?
The Core Concept: Side-effect logging.

The Why & How: It is invoked *after* the fallback UI has been rendered. It receives the error and, crucially, the React component stack trace (showing exactly where in the JSX tree the error occurred). It should never be used to update state.

Real-World Scenario: In an enterprise app, you use this method to quietly format the stack trace and POST it to Datadog or Sentry. This allows the engineering team to receive an instant slack alert detailing exactly which component blew up in production without relying on user bug reports.
Q57
What is the “react-error-boundary” library and why is it an industry standard?
The Core Concept: It abstracts the boilerplate of Class boundaries into a flexible, highly reusable functional component wrapper.

The Why & How: It provides a simple <ErrorBoundary> component that accepts a `FallbackComponent` prop. More importantly, it provides the useErrorBoundary hook, allowing functional components to imperatively throw errors to the nearest boundary.

Real-World Scenario: Instead of writing a 50-line class component, you just wrap a feature: <ErrorBoundary FallbackComponent={ChartErrorState}> <HeavyChart /> </ErrorBoundary>.
Q58
How do you architect a system to “reset” an Error Boundary?
The Core Concept: Giving users a self-service recovery UX.

The Why & How: An error boundary is stuck in its fallback state until it unmounts. To clear it, you must provide a mechanism to reset its internal state (setting `hasError` to false) and simultaneously reset the conditions that caused the error.

Real-World Scenario: A data grid crashes due to a temporary network timeout. The boundary catches it and displays a “Failed to load data. [Try Again]” button. Clicking the button calls the boundary’s reset function, which clears the error state and forcefully re-mounts the data grid, giving the network request a second chance to succeed.
Q59
Is it better to have one global Error Boundary or multiple localized ones?
The Core Concept: Blast-radius containment.

The Why & How: Relying on a single top-level boundary means any minor crash takes down the entire application. Architecturally, you should have a global boundary for catastrophic routing failures, but wrap distinct features in localized boundaries to isolate crashes.

Real-World Scenario: Building a complex layout like Figma. The toolbars, the canvas, and the layer sidebar should all have independent boundaries. If a user installs a buggy plugin that crashes the layer sidebar, that sidebar shows an error state, but the user can still safely save their work on the main canvas.
Q60
How can you force an asynchronous error to be caught by an Error Boundary?
The Core Concept: The state-setter hack.

The Why & How: Since boundaries ignore async `fetch` errors, you can catch the error in your promise chain, and then intentionally pass it to a React state setter function configured to throw.

Real-World Scenario: const [_, setError] = useState(). In your fetch catch block, you execute: setError(() => { throw new Error(err) }). Because state setter callbacks are executed during React’s render phase, throwing inside it successfully trips the Error Boundary layout higher up the tree.

7. Data Fetching & Asynchronous UI

Q61
Why are libraries like React Query or SWR preferred over raw useEffect for data fetching?
The Core Concept: They shift the paradigm from “imperative data fetching” to “declarative cache management.”

The Why & How: Writing a fetch inside a useEffect means you have to manually code loading states, error states, and cache storage. Libraries like React Query handle deduplication of identical requests, background revalidation, retry logic, and pagination automatically out of the box.

Real-World Scenario: A user clicks between the “Home” tab and “Profile” tab rapidly. With useEffect, the app makes a new API call every single time they click “Profile”, showing a loading spinner. With React Query, the data is cached; clicking “Profile” shows the UI instantly, while the library quietly checks the server in the background for updates.
Q62
What is a race condition in asynchronous React data fetching?
The Core Concept: A race condition occurs when a component fires multiple asynchronous requests in rapid succession, but the network resolves them out of order.

The Why & How: Network latency is unpredictable. If Request A (slow) is fired, and then Request B (fast) is fired, B might arrive and set the component state first. Then A arrives late and overwrites B, leaving the UI displaying stale or incorrect data.

Real-World Scenario: You have a dropdown to filter users by department: “Sales” then “Engineering”. You click Sales, the API is slow. You quickly click Engineering, the API is fast, and the UI populates with Engineers. A second later, the delayed “Sales” data arrives and overwrites the UI, even though the dropdown still visibly says “Engineering”.
Q63
How do you architecturally solve race conditions inside a useEffect?
The Core Concept: Request cancellation via the browser’s native AbortController.

The Why & How: You instantiate an AbortController inside the effect and pass its `signal` to the fetch request. Crucially, you call controller.abort() inside the useEffect cleanup function. When the dependency array changes (e.g., the user changes the filter), the cleanup function runs, immediately killing the pending network request before the new one fires.

Real-World Scenario: Going back to the department filter example: When the user clicks “Engineering”, the effect’s cleanup function runs, which instantly cancels the hanging “Sales” network request at the browser level, ensuring it can never resolve and corrupt your UI state.
Q64
What role does React Suspense play in data fetching architectures?
The Core Concept: Suspense allows a component to “pause” its rendering while it waits for an asynchronous operation to resolve, delegating the loading state to a parent boundary.

The Why & How: It eliminates the need for `if (isLoading) return ` inside every component. Supported libraries (like React Query or Relay) will “suspend” the component when data is missing. The nearest parent <Suspense fallback={<SkeletonLoader/>}> catches this suspension and displays the UI.

Real-World Scenario: A dashboard has three distinct widgets: Sales, Traffic, and Alerts. Instead of three separate spinners jumping around, you wrap all three in a single <Suspense> boundary. The dashboard shows one clean, unified loading skeleton until *all* the widgets have fetched their data.
Q65
What is Optimistic UI updating?
The Core Concept: A UX pattern where the UI is updated *immediately* when a user takes an action, assuming the server request will succeed, before actually waiting for the network response.

The Why & How: It makes the app feel instantly responsive. You mutate the local cache immediately. If the subsequent `POST` or `PUT` request to the server fails, you catch the error and cleanly “roll back” the UI cache to its previous state.

Real-World Scenario: Clicking the “Like” heart on a tweet. The heart turns red instantly. In the background, an API request is fired. If you lose internet and the request fails, the app quietly rolls back the heart to gray and shows a tiny “Failed to like” toast, rather than making you wait 500ms for the heart to turn red.
Q66
How does SWR (Stale-While-Revalidate) handle data caching strategies?
The Core Concept: It prioritizes immediate UI rendering by serving stale data while fetching fresh data in the background.

The Why & How: When a component mounts, SWR checks its memory cache. If data exists, it returns it instantly (the UI renders). Concurrently, it sends a network request to the server (revalidate). Once the fresh data arrives, it silently updates the cache and triggers a re-render only if the data actually changed.

Real-World Scenario: Opening a cryptocurrency portfolio app. You instantly see your balances from your last session 2 hours ago (Stale). Half a second later, the numbers tick up or down to reflect the live market prices (Revalidate). The user never sees a loading spinner.
Q67
How do you implement a highly performant Infinite Scroll?
The Core Concept: Integrating the browser’s native IntersectionObserver API with pagination state.

The Why & How: Listening to the `window.onscroll` event is terrible for CPU performance. Instead, you place an empty <div id="load-more-trigger"> at the bottom of your list. You use an Intersection Observer to watch that div. When it enters the viewport, it triggers a fetch for `page + 1` and appends the results to your state array.

Real-World Scenario: Building an Instagram-style feed. You render 10 posts. At the bottom, you have an invisible trigger div. As the user scrolls past post 8, the trigger enters the screen, firing the API for the next 10 posts. They append seamlessly, creating a frictionless scrolling experience.
Q68
How do you safely integrate WebSockets into a React component?
The Core Concept: Managing the persistent connection lifecycle within the bounds of React’s mount/unmount cycle.

The Why & How: You initialize the `new WebSocket(url)` inside a useEffect with an empty dependency array to ensure it only connects once. You bind the `onmessage` event to update your state. Crucially, the cleanup function *must* call `socket.close()`.

Real-World Scenario: A live chat widget. If you don’t close the socket in the cleanup function, and the user navigates away from the chat page and comes back, a second WebSocket connection is opened. Now, every time someone sends a message, your app receives it twice, causing duplicate messages in the UI.
Q69
What is the primary architectural benefit of Apollo Client for GraphQL in React?
The Core Concept: Normalized, entity-based caching rather than endpoint-based caching.

The Why & How: REST libraries cache the exact JSON string returned by a specific URL. Apollo Client unpacks GraphQL responses, looks at the __typename and id of every object, and stores them in a flat, normalized lookup table. This ensures extreme data consistency across the entire app.

Real-World Scenario: Your <Header> queries for User { id: 1, name: "Alex" }. Your <SettingsPage> fires a mutation changing the name to “Alexander”. Because Apollo normalizes by ID, the moment the mutation succeeds, the Header automatically re-renders with the new name without needing to be manually refetched.
Q70
Why must you debounce search input network requests, and how is it implemented?
The Core Concept: Limiting the rate at which a function can fire to protect backend infrastructure and reduce client-side network thrashing.

The Why & How: If you fetch data `onChange`, a fast typist spelling “React” triggers 5 distinct API calls in 200ms. Debouncing uses a timeout to delay the API call until the user has stopped typing for a specific duration (e.g., 300ms).

Real-World Scenario: You implement a custom hook `useDebounce(searchTerm, 300)`. The hook returns a delayed version of the search term. You put that *debounced* term in your useEffect dependency array. The user types “React”. The state updates instantly, but the network request only fires once, 300ms after they hit the “t”.

8. React 19 Intermediate Features

Q71
How does the useTransition hook change how React renders?
The Core Concept: It allows developers to mark specific state updates as “non-urgent” (transitions), enabling concurrent rendering without blocking the main thread.

The Why & How: Normally, React state updates are urgent and lock the UI until finished. By wrapping a heavy state update inside startTransition(() => setList(heavyData)), React will process it in the background. If the user clicks a button while it’s processing, React pauses the heavy task, handles the click, and resumes the task.

Real-World Scenario: A search page with a massive map. Typing in the search input is urgent (you want to see the letters instantly). Filtering the 10,000 map pins is non-urgent. You wrap the map filtering state update in a transition. The user can type fluidly without the map calculation causing the keyboard to freeze.
Q72
What is the architectural difference between useTransition and useDeferredValue?
The Core Concept: Both manage non-urgent rendering, but they differ in *where* you have control of the data flow.

The Why & How: useTransition wraps the actual state setter function, meaning you use it when you *own* the state update. useDeferredValue wraps a value passed down as a prop, meaning you use it when you are receiving data from a parent and cannot control how the state was originally set.

Real-World Scenario: If you own the search input and the map, use useTransition on the input change handler. If you are just building the <MapDisplay pins={data} /> component and receiving data from a generic layout wrapper, you use const deferredPins = useDeferredValue(pins) to protect your component from blocking the main thread.
Q73
How does Concurrent Rendering actually handle “interruptions”?
The Core Concept: React uses a priority queue and double-buffering for its Virtual DOM.

The Why & How: When a non-urgent task (a transition) starts, React begins building the new DOM tree in memory (the “work-in-progress” tree). If an urgent task (like a click) occurs, React completely throws away the half-finished background tree, instantly builds and commits a tree for the urgent click, and then starts the background task over from scratch.

Real-World Scenario: Hovering over a complex tooltip triggers a massive graph to render in the background. If the user moves their mouse away before the graph finishes rendering, React simply aborts the calculation. The main thread is never blocked, and no wasted DOM mutations occur.
Q74
What is the purpose of the new `use()` API, and how does it replace older fetching patterns?
The Core Concept: use() is a special API that allows you to directly read the value of a Promise (or Context) during the render phase.

The Why & How: Unlike hooks, use() can be called conditionally (inside `if` statements). When you pass a Promise to it, it hooks directly into Suspense, pausing the component until the promise resolves. It completely replaces the need for basic useEffect fetching and `isLoading` state variables.

Real-World Scenario: A parent Server Component passes a pending userPromise down to a Client Component. Instead of setting up effects, the client simply calls const user = use(userPromise). The component suspends, the nearest boundary shows a spinner, and when the promise resolves, the component renders the user data.
Q75
What is Document Metadata Hoisting in React 19?
The Core Concept: React now natively supports rendering <title>, <meta>, and <link> tags from anywhere deep within the component tree.

The Why & How: Previously, you needed third-party libraries like `react-helmet` to manage head tags. Now, if a deeply nested <BlogPost> component renders a <title>My Post</title>, React automatically intercepts it and “hoists” it up into the document’s actual <head> tag.

Real-World Scenario: You are building a dynamic e-commerce catalog. Inside the <ProductDetail> component, you directly render <meta name="description" content={product.summary} />. React handles moving it to the document head for perfect SEO, without requiring you to pass that data up 10 levels to a layout component.
Q76
How do Server Actions fundamentally change form submissions in React 19?
The Core Concept: Server Actions integrate asynchronous backend logic directly into native HTML <form action={...}> props.

The Why & How: You can pass a server-side function directly to a form’s action prop. React handles the API layer, the serialization, and the network request automatically. If JavaScript is disabled or still loading (Progressive Enhancement), the browser falls back to a standard form POST, ensuring the feature always works.

Real-World Scenario: A “Subscribe to Newsletter” form at the bottom of a heavy landing page. A user scrolls down and hits submit before the heavy 2MB React JS bundle finishes executing. Because it’s a Server Action, the browser intercepts the submit and sends the data to the server anyway, never losing a conversion.
Q77
What are the new preload and prefetchDNS APIs?
The Core Concept: React 19 provides imperative APIs to instruct the browser to proactively load resources before the user actually needs them.

The Why & How: Calling preload('https://fonts.../style.css', { as: 'style' }) or prefetchDNS('https://api.backend.com') during a render phase injects the proper resource hints into the document head, allowing the browser to resolve domains or fetch assets during idle time.

Real-World Scenario: A user hovers their mouse over a “View 3D Model” button. You trigger a preload() for the massive Three.js script. By the time they actually click the button a half-second later, the script is already halfway downloaded, drastically reducing the perceived loading time.
Q78
How has handling Refs changed in function components in React 19?
The Core Concept: The forwardRef Higher-Order Component has been deprecated. Refs are now just standard props.

The Why & How: Previously, passing a ref from a parent to a child required wrapping the child in React.forwardRef(), which created messy code and complicated generic typings. In React 19, you simply destructure it: function CustomInput({ ref, placeholder }) { return <input ref={ref} /> }.

Real-World Scenario: Building a design system <Button> that needs to expose its DOM node for tooltip positioning. You no longer have to wrap the entire component file in a forwardRef HOC; you just pass ref down the prop chain exactly like you would an onClick handler.
Q79
What is the useOptimistic hook?
The Core Concept: A built-in hook designed specifically to manage optimistic UI updates without needing complex third-party cache managers.

The Why & How: You pass the “true” base state into useOptimistic. While a Server Action or async task is pending, the hook returns an optimistic value that you render. Once the action resolves (or fails), the hook automatically discards the optimistic value and snaps back to the source of truth, handling rollbacks inherently.

Real-World Scenario: A todo list app. The user clicks “Delete Task”. You pass the action to useOptimistic, which instantly removes the item from the array in the UI. If the server throws a 500 error, the hook automatically restores the deleted item to the list, requiring zero manual error-catching logic from you.
Q80
What is the React Compiler, and how does it alter the use of memoization hooks?
The Core Concept: It is a build-time tool (formerly “React Forget”) that automatically optimizes React components, effectively deprecating manual memoization.

The Why & How: Instead of developers deciding when to use useMemo, useCallback, or React.memo, the compiler analyzes your code’s data flow during the build step. It automatically inserts memoization logic precisely where needed to prevent unnecessary re-renders, based strictly on the rules of React.

Real-World Scenario: You have a deeply nested component tree suffering from performance issues due to inline object creation. Instead of spending two days refactoring the codebase with `useMemo` dependency arrays, you run the project through the React Compiler. It automatically memoizes the inline objects, instantly fixing the performance drops with zero code changes.

9. Testing React Apps

Q81
What is the core philosophy behind React Testing Library (RTL), and how does it differ from Enzyme?
The Core Concept: RTL enforces testing software exactly the way users interact with it, rather than testing the internal implementation details of a framework.

The Why & How: Enzyme allowed developers to assert “does this component have a state variable named `isOpen` set to true?” RTL forces you to assert “is the text ‘Welcome’ currently visible on the screen?” If you refactor a component to use useReducer instead of useState, RTL tests will perfectly survive the refactor, whereas Enzyme tests would completely break.

Real-World Scenario: A user doesn’t care if you use Redux, Context, or local state. They care that when they click the button labeled “Submit”, a success message appears. RTL guarantees that your tests verify this user-centric outcome.
Q82
Architecturally, when do you use getBy, queryBy, and findBy in RTL?
The Core Concept: They are three distinct query types used for specific assertion states.

The Why & How:
* getBy: Throws an error instantly if the element is not found. Use it as the default for elements that *must* be present.
* queryBy: Returns `null` instead of throwing. Use it strictly to assert that an element does *not* exist on the page.
* findBy: Returns a Promise and retries the query over time. Use it when waiting for an element to appear after an asynchronous event.

Real-World Scenario: When a page loads, you use getByRole('button') to click submit. You use queryByText('Error') and expect it to be null. Finally, you await findByText('Success') to catch the toast notification that renders half a second later.
Q83
Why is Mock Service Worker (MSW) preferred over directly mocking Axios/Fetch in unit tests?
The Core Concept: MSW intercepts HTTP requests at the actual network level within Node/JSDOM, rather than patching application-level code.

The Why & How: Writing `jest.mock(‘axios’)` couples your test tightly to the Axios library. If you migrate your app to the native `fetch` API tomorrow, every single one of your tests will break, even though the UI logic didn’t change. MSW spins up a mock server that intercepts the outbound HTTP request regardless of the client library used.

Real-World Scenario: You are testing a complex checkout component that uses React Query, Axios, and custom retry logic. MSW perfectly simulates network latency, 500 server errors, and successful JSON responses, testing the *entire* async infrastructure organically.
Q84
How do you test a component that inherently relies on a Context Provider?
The Core Concept: Components must be wrapped in their required Contexts during the test render, otherwise, hooks like useContext will return undefined and crash the test.

The Why & How: You do not wrap every individual test manually. Instead, you create a custom render utility function (e.g., `customRender`) that inherently wraps the tested component inside a standardized <AllTheProviders> component (including Theme, Redux, and Auth).

Real-World Scenario: You write a test for <ProfileAvatar />. In your test file, you just call render(<ProfileAvatar />). Behind the scenes, your custom render intercepts it and actually mounts <ThemeProvider><AuthProvider><ProfileAvatar /></AuthProvider></ThemeProvider>, ensuring the avatar has the correct data to display.
Q85
What is the purpose of act() in React testing, and when should you manually use it?
The Core Concept: act() is a helper that ensures all React updates (rendering, state updates, effects) triggered by an event are fully completed and flushed to the DOM before your test makes assertions.

The Why & How: RTL’s built-in functions (like userEvent.click or render) already have act() built into them. You only need to use it manually if you have custom asynchronous logic (like a stray setTimeout or a mocked timer) updating React state outside the purview of the standard testing library tools.

Real-World Scenario: You see the dreaded warning: “An update to Component inside a test was not wrapped in act(…)”. This usually means your component fired a `fetch` request, the test finished and unmounted, and *then* the fetch resolved and tried to set state. You fix this by awaiting the proper DOM changes (using `findBy`) before letting the test end.
Q86
What are the architectural pros and cons of Snapshot Testing?
The Core Concept: Snapshot testing takes a serialized string copy of your component’s HTML output and compares it against a saved master copy to detect unintended visual regressions.

The Why & How:
* **Pros:** Very fast to write; alerts you to unexpected side-effects when changing shared CSS classes or layout wrappers.
* **Cons:** Highly brittle. Changing a typo from “Teh” to “The” fails the test. Because they fail so often on valid changes, developers develop “snapshot fatigue” and blindly run `jest -u` to update them without actually checking the diff, rendering the tests useless.

Real-World Scenario: Architects rarely use snapshots for raw UI components anymore. They are much better suited for testing complex, non-UI data transformations, like verifying the massive nested JSON configuration object generated by a custom form builder.
Q87
Why is UserEvent strongly preferred over fireEvent in modern RTL?
The Core Concept: fireEvent dispatches a single, raw DOM event. userEvent fully simulates the holistic browser interaction exactly as a human would execute it.

The Why & How: If you use fireEvent.change() on an input, it simply updates the value attribute. If you use userEvent.type(), it simulates the mouse hovering, the element focusing, and the sequence of `keydown`, `keypress`, and `keyup` for every single letter typed.

Real-World Scenario: You build a custom credit card input that auto-formats numbers. If you use `fireEvent`, your test might pass. But in reality, a bug in your custom `onKeyDown` handler might prevent users from using the Backspace key. `userEvent` will catch this bug because it rigorously fires the full event lifecycle.
Q88
How do you architecturally test asynchronous state updates in React components?
The Core Concept: Pausing the test runner execution until the expected DOM mutation finally occurs.

The Why & How: Because state updates and `useEffects` take time, an assertion directly after a click will fail (checking for data while the spinner is still on screen). You must use RTL’s async utilities like waitFor() (to retry an assertion until it passes) or findBy* (which combines `getBy` with `waitFor`).

Real-World Scenario: You test a login form. You `userEvent.click(submitButton)`. You cannot immediately assert the dashboard is visible. You must write: expect(await screen.findByText('Welcome to your Dashboard')).toBeInTheDocument(). RTL will poll the DOM every 50ms until the API resolves and the text appears.
Q89
What does 100% Code Coverage mean, and why might an Architect advise against it?
The Core Concept: It is a metric indicating that every single line, branch, and function in your codebase was executed at least once by a test runner.

The Why & How: While coverage is a useful metric to find glaring blind spots, mandating 100% is an anti-pattern. It forces developers to write meaningless, brittle tests just to hit the number (like testing native getter/setters or static constants), slowing down feature velocity without actually improving software quality.

Real-World Scenario: A pragmatic architect aims for 70-80% coverage. They mandate 100% coverage on the complex Redux billing reducer that processes money, but perfectly accept 20% coverage on a static UI component that just renders a marketing banner.
Q90
How does E2E testing (Cypress/Playwright) differ fundamentally from Component Integration testing (RTL)?
The Core Concept: JSDOM vs a Real Rendering Engine.

The Why & How: RTL runs in Node using JSDOM, a mocked version of a browser. JSDOM does not paint pixels, calculate CSS layouts, or have a real network stack. E2E tests spin up an actual Chromium/WebKit browser, click real pixels, and hit real staging databases.

Real-World Scenario: You write an RTL test to ensure a `` works. It passes. However, in production, a `z-index` CSS bug caused a transparent absolute div to cover the button. RTL cannot detect this because JSDOM doesn’t render CSS. Playwright will fail the test and catch the bug because its virtual mouse is physically blocked from clicking the button.

10. SSR, Next.js & Ecosystem

Q91
What is the fundamental difference between Server-Side Rendering (SSR) and Client-Side Rendering (CSR)?
The Core Concept: The location where the initial HTML is assembled.

The Why & How: In CSR (standard React), the server sends a blank HTML shell containing a <script> tag. The browser downloads the JS bundle and uses it to construct the DOM from scratch. In SSR, a Node.js server executes the React components, generates the fully populated HTML string, and sends that complete document to the browser.

Real-World Scenario: A user on a slow 3G mobile connection visits a news site. With CSR, they stare at a blank white screen for 4 seconds while the massive JS bundle downloads. With SSR, they see the fully formatted text of the article in 400 milliseconds, even before the interactive JavaScript has finished downloading.
Q92
Architecturally, what is Static Site Generation (SSG) and when is it superior?
The Core Concept: Generating HTML on a build server once, rather than on a web server per user request.

The Why & How: During the CI/CD pipeline (e.g., next build), React fetches the required data, renders all the pages to static HTML files, and uploads them to a global CDN. It completely eliminates database hits and server compute time for end-users, offering the fastest possible Time To First Byte (TTFB).

Real-World Scenario: A corporate marketing site or a developer blog. The content only changes when a new article is published. Instead of running an expensive Node server to SSR the same article 10,000 times a day, SSG builds it once. Users globally pull the static HTML file instantly from a CDN node in their city.
Q93
What is Incremental Static Regeneration (ISR) in Next.js, and what problem does it solve?
The Core Concept: ISR allows you to update specific static pages in the background *after* the initial deployment, without needing to rebuild the entire application.

The Why & How: You configure a revalidate time (e.g., 60 seconds). A user requests a page, getting the lightning-fast static CDN cache. If 60 seconds have passed since the last cache, Next.js triggers a background serverless function to quietly rebuild that specific page. The *next* user gets the newly generated page.

Real-World Scenario: An e-commerce store with 2 million products. Running SSG for 2 million pages would take hours. Using SSR for every page view would crash the database. With ISR, popular product pages are instantly served statically, but if a marketing team changes a price, the cache automatically refreshes itself a minute later without a full site deploy.
Q94
Explain the concept of “Hydration” in React SSR architectures.
The Core Concept: The process of attaching interactivity and state management to “dead” HTML previously rendered by a server.

The Why & How: The server sends HTML to show the user the UI quickly, but HTML cannot handle onClick events or useState. The browser downloads the React JS bundle in the background. Once loaded, React traverses the existing DOM, “wakes it up”, and binds all the event listeners, turning the static document into a fully functional Single Page Application.

Real-World Scenario: The server renders a fancy “Add to Cart” button. It appears instantly on the screen. However, for a brief fraction of a second (before hydration completes), clicking the button does nothing. Once React finishes hydrating the tree, clicking it triggers the complex state logic.
Q95
What causes a Hydration Mismatch error, and how do you fix it?
The Core Concept: It occurs when the initial HTML tree generated by the Node server does not identically match the Virtual DOM tree that React expects to see on its first client-side render.

The Why & How: If React detects a difference, it abandons the server HTML and forcefully re-renders the entire component tree from scratch, destroying performance. This usually happens when relying on browser-specific APIs (like window.innerWidth) or dynamic data (like new Date()) during the initial render.

Real-World Scenario: You render a greeting: <h1>Good {new Date().getHours() > 12 ? 'Evening' : 'Morning'}</h1>. The server in London renders “Morning”. The client opens it in Tokyo where it’s night, so React renders “Evening”. The trees mismatch, and React throws an error. You fix this by only executing dynamic logic inside a useEffect, which only runs *after* hydration is complete.
Q96
Architecturally, what is the core difference between the Next.js App Router and the legacy Pages Router?
The Core Concept: The App Router shifts the entire foundation to React Server Components (RSC).

The Why & How: In the Pages router, data fetching was route-level (getServerSideProps). You fetched everything at the top and prop-drilled it down. In the App Router, every component is a Server Component by default. You can fetch database queries directly inside deeply nested components (like a Sidebar), bypassing API layers entirely.

Real-World Scenario: Building a layout with a user profile and a shopping cart. In Pages, the top layout.js had to fetch both pieces of data and pass them down. In App Router, the <UserProfile> component connects to the DB itself, and the <Cart> component connects to Redis itself. They are decoupled and stream to the client independently.
Q97
When exactly do you use the “use client” directive in Next.js?
The Core Concept: It establishes a boundary, opting a component (and all its children) out of the Server Component architecture and into standard Client-Side rendering.

The Why & How: Because App Router components run on the server by default, they cannot use interactivity. You add "use client" *only* to components that require React hooks (useState, useEffect), DOM event listeners (onClick), or browser-exclusive APIs (window.localStorage).

Real-World Scenario: A blog post page. The layout, the article text, and the heavy Markdown parser should remain Server Components (0kb JS sent to the client). You only add "use client" to the tiny LikeButton.tsx component at the bottom, so it can manage its own isLiked state and onClick animation.
Q98
How do React Server Components (RSC) differ fundamentally from traditional SSR?
The Core Concept: Traditional SSR sends both the HTML *and* the Javascript bundle. RSCs never send their Javascript to the browser at all.

The Why & How: SSR is a technique to generate initial HTML, but the component still mounts on the client for hydration. An RSC executes *exclusively* on the server. Its code is completely stripped from the final client bundle, drastically reducing the amount of JavaScript the user has to download, parse, and execute.

Real-World Scenario: You install the massive date-fns library to format timestamps in a list of comments. In traditional SSR, the user downloads date-fns.js just in case the component re-renders. With an RSC, the server runs date-fns, formats the string, and sends *only* the raw HTML string <span>Jan 1st</span>. The user never downloads the library.
Q99
Why do plain React SPAs (Client-Side Rendering) struggle with SEO?
The Core Concept: Web crawlers (like Googlebot or Twitter preview bots) parse HTML, and CSR apps serve virtually empty HTML files.

The Why & How: When a crawler hits a standard React app, it sees <div id="root"></div> and no meaningful metadata. While Googlebot eventually spins up a headless browser to execute the JS and read the rendered content, it takes significantly longer, is deprioritized, and many social media scrapers (like Slack or iMessage) simply give up and show a blank preview card.

Real-World Scenario: If you paste an Amazon product link in Discord, you see the product image and price instantly. This is because the server sent that metadata natively in the HTML <head>. A raw CSR React app cannot do this effectively because the router hasn’t loaded to figure out which product you are looking at yet.
Q100
How do you securely handle Authentication state in SSR applications?
The Core Concept: The server cannot read localStorage, so tokens must be stored in secure HTTP cookies.

The Why & How: In a CSR app, you can save a JWT in localStorage. But when a user requests an SSR page, the Node server needs to know *before* it generates the HTML if the user is an admin or not. Browsers automatically attach cookies to every network request, allowing the server to read the cookie, validate the session, and conditionally render the correct HTML shell.

Real-World Scenario: A user navigates to /admin/dashboard. The browser sends the request along with an HttpOnly, Secure cookie containing their session ID. Next.js middleware intercepts the request, verifies the cookie against the database, and either allows the server to render the sensitive dashboard HTML, or immediately redirects them to /login with a 302 response.

React 100: The Field Guide

100 crucial beginner-level React JS interview questions and answers. Read through full-width, mobile-optimized topics covering Hooks, Rendering, and the new React 19 updates.

1. React Basics & JSX

Q1
What is React, and what problem does it solve?
React is a JavaScript library for building user interfaces, created by Facebook (Meta). It solves the problem of keeping the UI in sync with changing data by letting you describe what the UI should look like for a given state, and React handles updating the actual DOM efficiently behind the scenes.
Q2
What is JSX?
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like markup directly inside JavaScript. It makes component code easier to read and write, and it gets compiled into regular React.createElement() calls before running in the browser.
Q3
Why can’t browsers run JSX directly?
Browsers only understand plain JavaScript, not JSX syntax. Tools like Babel, or the compiler built into bundlers such as Vite or webpack, transpile JSX into React.createElement() (or the newer jsx() runtime) calls that browsers can execute.
Q4
What is the Virtual DOM?
The Virtual DOM is a lightweight in-memory representation of the real DOM, kept as a tree of plain JavaScript objects. React updates this virtual tree first, compares it to the previous version, and then applies only the necessary changes to the real DOM.
Q5
How does the Virtual DOM improve performance compared to direct DOM manipulation?
Direct DOM updates are expensive because the browser has to recalculate layout and repaint. By comparing virtual trees first (a process called diffing) and batching changes, React figures out the minimal set of real DOM operations needed and applies them together, reducing costly reflows.
Q6
What is the difference between React and ReactDOM?
react contains the core library: components, hooks, and the logic for describing UI. react-dom is the renderer that knows how to take that description and mount it into a web page’s DOM (a separate package, react-native, renders to native mobile views instead).
Q7
What is the difference between a React element and a React component?
A React element is a plain, immutable object describing what to render, usually created via JSX. A component is a function (or class) that returns elements; elements are the blueprint output, and components are the factories that produce them.
Q8
Can you write JavaScript expressions inside JSX, and how?
Yes, by wrapping the expression in curly braces, e.g. <p>{user.name}</p> or <p>{2 + 2}</p>. Only expressions are allowed, not statements, so you can’t put an if block directly inside braces, though ternaries and logical operators work fine.
Q9
How do you return multiple sibling elements from a component without adding an extra wrapper div?
Wrap them in a React Fragment, written as <React.Fragment>...</React.Fragment> or the shorthand <>...</>. Fragments satisfy JSX’s one-root-element rule without adding any extra node to the actual DOM.
Q10
Is Create React App still the recommended way to start a new React project?
No. Create React App is no longer actively maintained and isn’t recommended by the React team. Modern projects typically start with Vite (npm create vite@latest) for plain React apps, or a framework like Next.js or Remix when server rendering and routing are needed.
Q11
Why is React called a library rather than a framework?
React focuses on one job, rendering UI from components, and deliberately leaves routing, state management, and data fetching to other libraries you choose yourself. Frameworks bundle a more complete, opinionated set of these tools together.
Q12
What is a single-page application (SPA), and how does React help build one?
An SPA loads a single HTML page and then updates content dynamically with JavaScript instead of requesting a new page from the server on every navigation. React’s component model and virtual DOM make it well suited to re-render just the parts of the page that change.

2. Components & Props

Q13
What is a component in React?
A component is a reusable, self-contained piece of UI, written as a JavaScript function (or class) that accepts inputs called props and returns JSX describing what should appear on screen.
Q14
What’s the difference between functional and class components?
Functional components are plain JavaScript functions that return JSX and use hooks for state and side effects. Class components extend React.Component, manage state with this.state, and use lifecycle methods like componentDidMount instead of hooks.
Q15
Why are function components preferred over class components today?
Function components are shorter, easier to read, and avoid the confusing behavior of the this keyword. Hooks also let you share stateful logic between components more easily than the older patterns class components required.
Q16
What are props in React?
Props (“properties”) are read-only inputs passed from a parent component into a child component, similar to function arguments. They let you customize a component’s content or behavior without changing the component’s own code.
Q17
Are props mutable or immutable?
Props are immutable from the child’s perspective. A component should never reassign or modify its own props directly; if a value needs to change, that change should happen in the parent that owns the state, then flow back down as a new prop.
Q18
How do you set default values for props?
In modern React, give the destructured parameter a default value directly: function Button({ size = 'medium' }) {...}. The older Component.defaultProps = {...} pattern still works but is being phased out.
Q19
What is props drilling, and why can it be a problem?
Props drilling is passing a prop down through several layers of components that don’t use it themselves, just to get it to a deeply nested child. It makes components harder to reuse and refactor, since unrelated components get coupled by data they don’t need.
Q20
What is the children prop?
children is a special prop containing whatever is nested between a component’s opening and closing tags, e.g. <Card>Hello</Card> gives Card a children prop equal to “Hello”. It’s commonly used to build wrapper or layout components.
Q21
Can a component return null? What happens then?
Yes. Returning null (or false, or undefined) tells React to render nothing for that component, while the component itself stays mounted and can still respond to future prop or state changes.
Q22
What is component composition, and why is it preferred over inheritance in React?
Composition means building complex UI by combining smaller components together, often via the children prop, rather than extending a base component class. React’s team recommends composition because it’s more flexible and avoids fragile class hierarchies.

3. State, Events & Forms

Q23
What is state in React?
State is data that a component owns and manages internally, which can change over time, typically as a result of user interaction. Unlike props, state isn’t passed in from outside; updating it causes the component to re-render with the new value.
Q24
What’s the difference between props and state?
Props are passed into a component from its parent and are read-only from the receiving side. State is local data a component manages itself with hooks like useState, and only that component (or what it explicitly passes down) can change it.
Q25
How do you update state correctly in a function component?
You call the setter function returned by useState, e.g. const [count, setCount] = useState(0); setCount(count + 1);. Calling the setter, rather than reassigning the variable directly, is what tells React to schedule a re-render.
Q26
Why shouldn’t you mutate state directly, e.g. pushing into a state array?
React detects changes by comparing references, not deep contents, so mutating an object or array in place won’t trigger a re-render and can lead to stale UI. Instead, create a new array or object, for example with spread syntax, and pass that to the setter.
Q27
What happens when you call a state setter function?
React schedules a re-render of that component and its children with the new state value, batching it with other updates for efficiency. The re-render doesn’t happen synchronously the instant you call the setter; it happens before the next paint.
Q28
Why doesn’t a state variable show its new value immediately on the next line of code after calling its setter?
State updates are asynchronous and tied to the next render, while the variable you’re reading in the current call is a snapshot from the render already in progress. To act on the new value immediately, use it before calling the setter, or move that logic into a useEffect that runs after the re-render.
Q29
What is the difference between controlled and uncontrolled components in forms?
A controlled component has its value driven by React state, with value and onChange wired together so React is the single source of truth. An uncontrolled component lets the DOM manage its own value internally, read later via a ref when needed.
Q30
How do you handle a form input’s onChange event in React?
Attach an onChange handler that reads event.target.value and stores it in state, e.g. <input value={name} onChange={(e) => setName(e.target.value)} />. This keeps the input controlled by React state.
Q31
How do you handle form submission and prevent the browser’s default behavior?
Attach an onSubmit handler to the <form> element and call event.preventDefault() before running your own submission logic. Without that call, the browser would try to reload the page or navigate, which isn’t usually what you want.
Q32
What is a SyntheticEvent in React?
SyntheticEvent is React’s cross-browser wrapper around the browser’s native event object, normalizing differences between browsers so event handlers behave consistently. It mirrors the native event’s API, like target and preventDefault(), while integrating with React’s event system.
Q33
How do you pass an argument to an event handler in JSX?
Wrap the call in an arrow function so it isn’t invoked immediately during render, e.g. <button onClick={() => handleDelete(item.id)}>Delete</button>. Writing onClick={handleDelete(item.id)} without the arrow function would call it during render instead of on click.
Q34
What is the new way to handle forms in React 19 using Actions?NEW · 19
React 19 lets you pass a function directly to a form’s action prop (or a button’s formAction prop); React calls it automatically on submission and can handle pending and error states for you. This is often paired with the new useActionState hook to simplify form-handling boilerplate.

4. Hooks

Q35
What are hooks in React?
Hooks are functions, like useState and useEffect, that let function components use features such as state and side effects that used to only be available in class components. They always start with the word “use” by convention.
Q36
What are the rules of hooks?
Hooks must only be called at the top level of a function component or custom hook, never inside loops, conditions, or nested functions, and they must always be called in the same order on every render. This consistent order is how React tracks which state belongs to which useState call.
Q37
What does useState return?
It returns an array with exactly two elements: the current state value, and a setter function used to update that value, e.g. const [count, setCount] = useState(0).
Q38
Can you use multiple useState calls in a single component?
Yes, and it’s common practice. Splitting unrelated pieces of state into separate useState calls, instead of one big state object, usually makes components easier to read and update.
Q39
What is useEffect used for?
useEffect lets you run side effects, code that interacts with something outside of rendering, like fetching data, subscribing to events, or manually updating the document title, after the component renders.
Q40
What is the dependency array in useEffect?
It’s the second argument to useEffect, an array of values the effect depends on. React re-runs the effect only when one of those values has changed since the last render, instead of on every render.
Q41
What happens if you omit the dependency array entirely in useEffect?
The effect runs after every single render, which is rarely what you want and can cause performance issues or infinite loops if the effect itself triggers a state update.
Q42
What is a cleanup function in useEffect, and when is it called?
It’s a function you optionally return from inside your effect, used to undo things like subscriptions, timers, or event listeners. React calls it right before the component unmounts, and before re-running the effect due to a dependency change.
Q43
What is useContext, and what problem does it solve?
useContext lets a component read a value from a Context Provider higher up the tree without it being passed down manually through every intermediate component. It’s the main way to avoid props drilling for things like themes or authenticated user info.
Q44
What is useRef, and how is it different from useState?
useRef returns a mutable object with a .current property that persists across renders, but changing it does not trigger a re-render the way a state update does. It’s commonly used to reference a DOM node directly or store a value without re-rendering.
Q45
When would you use useRef instead of state?
Use useRef when you need to access a DOM element directly, such as focusing an input, or store a mutable value, like a timer ID or previous value, that shouldn’t cause the component to re-render whenever it changes.
Q46
What is useMemo, and when should you use it?
useMemo caches the result of an expensive calculation and only recomputes it when its dependencies change, instead of on every render. It’s best used when profiling shows a calculation is actually slow enough to matter, not by default on every computed value.
Q47
What is useCallback, and how is it different from useMemo?
useCallback memoizes a function itself so the same reference is reused across renders unless its dependencies change, while useMemo memoizes the return value of a computation. They’re often used together to avoid unnecessary re-renders of memoized child components.
Q48
What is a custom hook, and how do you create one?
A custom hook is a regular JavaScript function, starting with “use”, that calls other hooks inside it to encapsulate and reuse stateful logic across components, for example function useWindowWidth() {...} returning the current width.
Q49
What naming convention must custom hooks follow, and why?
Custom hook names must start with the lowercase word “use”, such as useFetch or useAuth. This convention lets React’s linter and rules-of-hooks checks correctly identify which functions are hooks so they can be checked for proper usage.
Q50
What is the use() hook introduced in React 19, and what makes it different from other hooks?NEW · 19
use() lets a component read the value of a Promise or a Context, and unlike other hooks, it can be called conditionally or inside loops. It’s often used to read data from a Promise passed down from a Server Component, letting the component wait for that value as part of rendering.

5. Lifecycle & Effects

Q51
What are the three phases of a component’s lifecycle?
Mounting (the component is created and inserted into the DOM), updating (the component re-renders due to changed props or state), and unmounting (the component is removed from the DOM).
Q52
What lifecycle phase does a useEffect with no dependency array correspond to?
It runs after every render, so it loosely corresponds to a combination of componentDidMount and componentDidUpdate running on every single update.
Q53
What lifecycle phase does useEffect with an empty dependency array correspond to?
It runs only once, right after the initial mount, similar to componentDidMount in class components, since an empty array means there are no dependencies that could ever change.
Q54
How do you replicate componentWillUnmount behavior in function components?
Return a cleanup function from inside useEffect. React calls that returned function when the component is about to unmount, or before re-running the effect again, which is the functional equivalent of componentWillUnmount.
Q55
What is the difference between useEffect and useLayoutEffect?
useEffect runs asynchronously after the browser has painted the screen, while useLayoutEffect runs synchronously after DOM mutations but before the browser paints. useLayoutEffect is reserved for rare cases like measuring layout and adjusting it before the user sees a flicker.
Q56
Why might an effect run twice in development mode with React 18 and later?
In development with StrictMode, React intentionally mounts, unmounts, and remounts components once to help you catch effects that aren’t cleaning up properly. This double-invoking only happens in development, not in production builds.
Q57
What is the danger of fetching data directly in the component body instead of inside useEffect?
Code that runs directly during render executes on every single render, so an unprotected fetch call there would re-trigger the request constantly and could cause infinite loops, especially if the fetch also sets state.
Q58
What is StrictMode, and why is it useful during development?
StrictMode is a wrapper component that renders no visible UI but activates extra checks, like double-invoking effects and detecting unsafe lifecycle usage, to help surface bugs in development before they reach production.

6. Lists, Keys & Conditional Rendering

Q59
How do you render a list of items in React?
Use JavaScript’s .map() to transform an array of data into an array of JSX elements, e.g. {items.map(item => <li key={item.id}>{item.name}</li>)}.
Q60
Why does React require a key prop when rendering lists?
Keys give React a stable identity for each item across renders, so it can correctly figure out which items were added, removed, or reordered instead of re-rendering the entire list from scratch.
Q61
Why is using the array index as a key sometimes problematic?
If the list can be reordered, filtered, or have items inserted or removed, the index of a given item changes even though the item itself didn’t, which can cause React to mismatch state or DOM nodes between the wrong items.
Q62
What’s a good key to use if your data doesn’t already have a unique id?
Generate one when the data is created, using something like crypto.randomUUID(), a database-assigned id once it’s saved, or a stable combination of fields guaranteed not to repeat, rather than relying on array position.
Q63
How do you conditionally render JSX based on a boolean?
Common approaches are a ternary expression, condition ? <A /> : <B />, the && operator for an either-render-or-nothing case, condition && <A />, or an early if/return before the main JSX in the component function.
Q64
What is the && trick for conditional rendering, and what is one pitfall of it?
Writing {count && <p>{count} items</p>} renders the JSX only if count is truthy. The pitfall is that if count is 0, JavaScript’s && still evaluates to 0, so React renders a literal 0 on the page instead of nothing.
Q65
How do you render a fallback UI when there’s no data, e.g. an empty array?
Check the array’s length before mapping, e.g. {items.length === 0 ? <EmptyState /> : items.map(...)}, so the user sees a helpful message instead of a blank section.
Q66
How do you render a list of components and pass each item’s data as props?
Map over the array and pass each item’s fields as props to a child component, e.g. {users.map(u => <UserCard key={u.id} name={u.name} email={u.email} />)}.

7. Context, Refs & Performance

Q67
What problem does the Context API solve?
Context lets you share a value, like a theme, logged-in user, or language setting, across many components at different nesting levels without manually passing it down as a prop through every component in between.
Q68
How do you create and provide a context?
Create it with const ThemeContext = createContext(defaultValue), wrap the part of your tree that needs it with <ThemeContext.Provider value={theme}>, and read it in any descendant with useContext(ThemeContext).
Q69
What is the simplified syntax for using Context as a provider directly in React 19?NEW · 19
React 19 allows rendering the context object itself as a provider, e.g. <ThemeContext value={theme}>, instead of writing out <ThemeContext.Provider value={theme}> every time, slightly reducing boilerplate.
Q70
How does Context help avoid props drilling?
Instead of threading a value through every intermediate component as a prop just so a deeply nested child can use it, that child can call useContext directly and read the value from the nearest matching Provider above it.
Q71
What is React.memo, and when should you use it?
React.memo wraps a component so React skips re-rendering it if its props haven’t changed, using a shallow comparison. It’s best applied to components that render often with the same props and are expensive enough that skipping the re-render is worth it.
Q72
What causes unnecessary re-renders in React?
Common causes include a parent re-rendering and passing new object, array, or function references as props on every render even when the values are logically the same, or state updates that don’t actually need to affect a particular branch of the tree.
Q73
What is reconciliation in React?
Reconciliation is the algorithm React uses to compare a newly rendered virtual DOM tree against the previous one and determine the minimal set of real DOM changes needed to bring them in sync, rather than rebuilding the whole DOM from scratch.
Q74
What is the purpose of forwardRef, and how has React 19 changed the need for it?NEW · 19
forwardRef lets a parent pass a ref through a custom component down to one of its underlying DOM nodes, since refs aren’t a regular prop by default in older React versions. In React 19, function components can accept ref as a normal prop directly, so wrapping them in forwardRef is no longer required in most cases.
Q75
What is lazy loading in React, and how do you implement it with React.lazy and Suspense?
Lazy loading delays loading a component’s code until it’s actually needed, reducing the initial bundle size. Wrap a dynamic import with React.lazy(() => import('./Component')) and render it inside a <Suspense fallback={<Spinner />}> boundary that shows a fallback while the code loads.
Q76
What is code splitting, and why does it matter even for beginners to know about?
Code splitting breaks an app’s JavaScript into smaller chunks that load on demand instead of one giant bundle upfront. Knowing it exists helps explain why some parts of an app might briefly show a loading state and why bundlers like Vite create multiple output files.

8. Routing & Ecosystem Basics

Q77
Does React include built-in routing? What do you use instead?
No, React itself has no built-in router. For multi-page-feeling SPAs, developers commonly add a separate library like React Router, or use a framework such as Next.js or Remix that includes routing out of the box.
Q78
What is the difference between client-side routing and a traditional page reload?
Client-side routing updates the URL and swaps which components are rendered using JavaScript, without making a fresh request to the server or reloading the whole page. A traditional page reload tears down the page and re-downloads everything from the server.
Q79
What is React Router, and what are its core building blocks?
React Router is the most widely used routing library for React. Its core pieces are <Routes> and <Route path="..." element={...} /> to define which component renders for a given URL, and <Link to="..."> to navigate between routes without a full page reload.
Q80
What is the difference between React Router’s Link and a normal anchor tag?
A normal <a> tag triggers a full browser page reload when clicked. <Link> intercepts the click and updates the route using JavaScript instead, preserving the SPA’s in-memory state and avoiding the cost of reloading the page.
Q81
What are some common state management options beyond useState and Context for bigger apps?
Popular choices include Redux Toolkit, Zustand, Jotai, and Recoil for general client state, plus libraries like React Query or SWR specifically for managing server and data-fetching state with caching.
Q82
Why does a React project need a package.json, and what role do npm, yarn, or pnpm play?
package.json lists a project’s dependencies, like react and react-dom, and scripts, like npm run dev. Package managers such as npm, yarn, or pnpm read that file to install the exact library versions a project needs and to run those scripts.

9. React 19 — What’s New

Q83
What is the headline feature of React 19 related to forms and async operations?NEW · 19
Actions: you can pass an async function directly to a <form action={...}> or a button’s formAction, and React automatically manages pending states, errors, and optimistic updates around it, removing a lot of manual onSubmit and loading-state boilerplate.
Q84
What is useActionState, and what problem does it solve?NEW · 19
useActionState takes an action function and an initial state, and returns the current state, a wrapped action to pass to a form, and a pending flag. It removes the need to manually wire up useState plus an onSubmit handler just to track a form’s result and loading status.
Q85
What is useFormStatus, and where can it be used?NEW · 19
useFormStatus returns the pending status, and submitted data, of the nearest parent <form>, but it must be called inside a component rendered as a descendant of that form, not the form’s own component. It’s handy for a reusable submit button that automatically disables itself while submitting.
Q86
What is useOptimistic, and what UX problem does it solve?NEW · 19
useOptimistic lets you show a temporary, optimistic version of state immediately while an async update is still in flight, then reconciles it with the real result once the server responds. It solves the lag between a user’s action, like liking a post, and the UI visibly reflecting it.
Q87
Can you use ref as a normal prop on function components in React 19 without forwardRef?NEW · 19
Yes. React 19 allows function components to receive ref directly as a regular prop, similar to any other prop, so forwardRef is no longer required for most simple ref-forwarding use cases, though it’s still supported for backward compatibility.
Q88
What is Document Metadata support in React 19?NEW · 19
You can render tags like <title>, <meta>, and <link> directly inside any component, even deep in the tree, and React automatically hoists them up into the document’s <head> rather than rendering them where they appear in the JSX.
Q89
What are the Asset Loading APIs introduced in React 19?NEW · 19
Functions like preload, preinit, preconnect, and prefetchDNS, from react-dom, let you hint to the browser to start fetching a stylesheet, font, script, or domain connection earlier, before the resource is actually needed, to speed up loading.
Q90
What are Server Components, briefly, and how do they differ from regular components?NEW · 19
Server Components render entirely on the server and send only the resulting UI description to the browser, with no extra JavaScript shipped for them. Regular client components run their JavaScript in the browser and can use state, effects, and event handlers, which Server Components cannot do directly.
Q91
What is the React Compiler, and what problem is it trying to solve?NEW · 19
The React Compiler is a build-time tool that automatically analyzes component code and inserts memoization, similar to what you’d write by hand with useMemo or useCallback, so components skip unnecessary re-renders without manual optimization.
Q92
Does the React Compiler replace useMemo and useCallback?NEW · 19
For most everyday cases, yes, since the compiler can apply equivalent optimizations automatically. You can still write useMemo or useCallback by hand for edge cases it doesn’t catch, but the goal is that beginners increasingly won’t need to reach for them manually.
Q93
What is a Server Action, and how does it relate to React 19’s Actions feature?NEW · 19
A Server Action is a function marked to run on the server, commonly with a use server directive in frameworks like Next.js, that a client component can call directly. It’s often wired up through the same action prop used by React 19’s form Actions, letting submissions trigger server-side logic without writing a separate API route by hand.
Q94
Is class component support removed in React 19?NEW · 19
No, class components still work in React 19. However, some already-deprecated APIs, like string refs and module-pattern factories, were removed, and the React team continues to recommend function components with hooks for any new code.

10. Tooling, Best Practices & Misc

Q95
What is the recommended way to start a new React project today instead of Create React App?
For a plain client-rendered React app, Vite, via npm create vite@latest with the React template, is the common recommendation. If routing, server rendering, or a fuller app structure is needed, a framework like Next.js or Remix is usually a better starting point.
Q96
What are React DevTools, and why are they useful?
React DevTools is a browser extension that lets you inspect the component tree, view and edit props or state live, and profile renders to find performance issues. It’s invaluable for debugging why a component re-rendered or why a prop isn’t updating as expected.
Q97
What is the difference between React and React Native?
React renders to the browser DOM for web applications, while React Native uses the same component and hooks model but renders to native mobile UI elements, like real iOS or Android views, instead of HTML, letting you build mobile apps with similar React concepts.
Q98
What’s a common beginner mistake when working with array state?
Calling array-mutating methods like .push(), .splice(), or .sort() directly on a state array. Since these mutate in place and don’t create a new array reference, React won’t detect the change; the fix is non-mutating approaches like [...array, newItem] or .filter()/.map() to build a new array.
Q99
Why is it risky to use the array index as both the rendering key and the identifier for delete or update operations?
If an item is deleted or reordered, every item after it shifts to a new index, so an index-based identifier no longer points to the same logical item; this can cause you to delete or edit the wrong item entirely, not just cause a rendering glitch.
Q100
What’s one general tip for approaching a React coding interview as a beginner?
Talk through your reasoning out loud as you go, such as why you chose useState over useRef, or why a particular key is safe to use, since interviewers are usually evaluating your understanding of why, not just whether the final code runs.