Production Incidents • State Architecture • Performance • Upgrades • Code Review • 2026

React Interview Questions for Experienced Candidates (5 Years)

React interviews for experienced candidates with around five years rarely ask what a hook is; they ask why you picked a pattern, what it cost you, and how you proved a fix worked. Expect stories about memory leaks, slow interactions, upgrades, shared components, code review and pushing back on a risky plan. It is written for React developers with roughly five to seven years behind them, who own a front-end module or a whole app, choose how state and data flow through it, get the call when a release breaks, and review other people's pull requests. Each answer below is a first-person story or a decision you can defend. Swap in your own project details.

Search all questions by round, difficulty and level, or save the ones you want to practice.

State and Data 3 questions

Hard System design round Mid-level, Senior Practice question

1. What state management setup did you choose for the app you owned, why that one, and which part of it aged badly?

What the interviewer is really testing:
Whether you sort state by kind (server data, URL, form, local UI, truly global) instead of dumping everything in one store, and whether you can own a choice that aged badly.
Answer frame:

Sort by kind: server data, URL state, form state, local UI state and the small bit that is truly global.

Home for each: a data cache for server data, the URL for filters, local state by default, a store only for the rest.

Regret: one honest thing you'd change and what it cost the team.

Sample spoken answer:

“On our admin app I split state by kind. Anything from the API went into a query cache, filters and page numbers went into the URL so links could be shared, form fields stayed inside the form, and only the signed-in user and a few settings lived in a global store. That kept the store tiny and made most components easy to test. What aged badly was the store itself. We let it grow a slice for every modal and wizard just in case, so a half-finished wizard would reappear on the next visit and nobody knew who owned those slices. I moved them back into local state and reset them on unmount, and those odd bugs stopped. The trade-off is another library for new joiners to learn, so I wrote a one-page guide on which state goes where.”

Red flag to avoid:

Saying everything goes in one global store because it is simpler, with no thought for server data or the URL.

They may ask next:
  • How do you stop the URL state and the cached data from disagreeing after a save?
  • What would make you put something in the global store after all?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

2. Why would you move API data out of a global store into a server-cache library, and what new problems did that bring?

What the interviewer is really testing:
Whether you understand that server data is a cache with its own problems (staleness, refetching, invalidation) and that a tool for it has costs too.
Answer frame:

Why: server data needs caching, deduping, refetch and invalidation, which a plain store makes you hand-write.

Gain: less boilerplate, shared requests, background refresh.

New problems: choosing stale times, invalidating the right keys, and query keys drifting between teams.

Sample spoken answer:

“Our store had a slice per API resource, each with its own loading, error and last-fetched fields, and three screens could fire the same request at once. Moving that data into a query cache gave us one request per key, background refresh, and retries for free, and it deleted a lot of reducer code. The new problems were real, though. Some screens showed old data because the default stale time didn't suit them, and after a save we sometimes forgot to invalidate a related list. I fixed that by keeping all query keys in one small module with helper functions, so a mutation could invalidate every list that shows the same record. I also set stale times per resource instead of one global number.”

Red flag to avoid:

Claiming a cache library makes staleness and invalidation go away on its own.

They may ask next:
  • How do you decide how long a piece of data is allowed to be stale?
  • When would you update the cache directly instead of invalidating and refetching?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

3. A page loads slowly because a parent fetches in an effect, then a child fetches in its own effect, then a grandchild does the same. How did you remove that waterfall?

What the interviewer is really testing:
Whether you can spot fetch-on-render waterfalls and move data loading earlier and in parallel, and what that does to component boundaries.
Answer frame:

Spot it: the network panel shows requests starting one after another, each waiting for a render.

Fix: start independent requests together, at the route level or in a loader, before the tree renders.

Trade-off: components own less of their own data, so be clear where data is loaded.

Sample spoken answer:

“On our order page, the order loaded first, then the items component mounted and fetched items, then each item fetched its stock. The network panel showed a staircase of requests, so the page took about three round trips longer than it needed to. The items request only needed the order id, which we already had from the URL. I moved loading to the route level, so the order and items requests start together as soon as the route matches, and with the backend team I added a stock endpoint that takes the order id, so one call replaced one per item and it starts at the same time too. Load time dropped a lot on slow networks. The trade-off is that the item component no longer fetches its own data, so I documented that route loaders own fetching on that page.”

Red flag to avoid:

Adding more loading spinners instead of changing when the requests start.

They may ask next:
  • How does Suspense change where the loading states live on a page like this?
  • When is it fine for a child component to fetch its own data?
Say it in 60 seconds

Upgrades and Migration 3 questions

Medium Behavioral round Mid-level, Senior Practice question

4. Tell me about moving a large React codebase from JavaScript to TypeScript. How did you plan it, and what did you make strict first?

What the interviewer is really testing:
Whether you can run a long migration in small safe steps that keep shipping features, and whether you know where types pay off first.
Answer frame:

Plan: allow JS and TS side by side, convert file by file, new files in TypeScript only.

Order: shared types for API data and shared components first, where one type protects many screens.

Tighten: turn on stricter checks step by step and track the count of loose types going down.

Sample spoken answer:

“Our app had a few hundred JavaScript files and we couldn't pause features, so I set the compiler to allow both languages and made one rule: new files are TypeScript, and any file you touch in a real change gets converted. I started with the API layer and the shared components, because typing an API response once protected every screen that used it, and typed props on shared components caught misuse across teams. Strict null checks came later, one folder at a time, because turning them on for everything at once produced thousands of errors. I tracked the number of any types each week so we could see progress. It took most of a year of background work. The cost was slower reviews early on, and the payoff was a clear drop in undefined crashes.”

Red flag to avoid:

Proposing a big-bang conversion that freezes features, or turning strict mode on everywhere on day one.

They may ask next:
  • How do you type API responses you don't fully trust at runtime?
  • What would you do about a teammate who fixes type errors by casting to any?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

5. Tell me about a major React version upgrade you led. What broke, and how did you roll it out without a scary release?

What the interviewer is really testing:
Whether you plan an upgrade in safe steps, know what really changed in the version, and can separate real breakages from development-only noise.
Answer frame:

Prepare: read the upgrade notes, fix old warnings and bump libraries that pin the old version first.

What changed: for React 18, createRoot, automatic batching everywhere and Strict Mode running effects twice in development.

Roll out: one step at a time behind a flag or on a small share of traffic, watching errors.

Sample spoken answer:

“I led our move to React 18. Before touching React, I cleared old lifecycle warnings and upgraded the libraries that didn't support it yet. Then I switched to createRoot and turned on Strict Mode. In development, Strict Mode mounts, unmounts and mounts again, and that exposed effects with no cleanup, like a store subscription that never unsubscribed and a timer that was never cleared. Those were real bugs, not noise. Automatic batching also changed one screen that read the DOM between two state updates inside a timeout, so I fixed that code instead of opting out. The TypeScript types dropped implicit children from function components, which touched a lot of files but was mechanical. We shipped it to internal users for a week before everyone. The cost was about two sprints, mostly on third-party libraries.”

Red flag to avoid:

Turning off Strict Mode to make the double effects go away instead of fixing the missing cleanups.

They may ask next:
  • How did you tell real bugs apart from harmless double calls in development?
  • How would you handle a key library that doesn't support the new version yet?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

6. You need to ship a full rewrite of the app's most used screen. How do you release it without a risky big-bang switch?

What the interviewer is really testing:
Whether you release big front-end changes gradually with flags, metrics and a fast way back.
Answer frame:

Flag: build the new screen next to the old one behind a feature flag.

Gradual: internal users first, then a small share of traffic, watching errors and key actions.

Way back: a kill switch, and delete the old screen only after it's been stable a while.

Sample spoken answer:

“When we rebuilt our main inbox screen, I kept the old one in the codebase and put the new one behind a feature flag checked at the route. Our own team used it for two weeks first and filed a lot of small bugs. Then we turned it on for a small slice of users and compared error rates, load time and how often people completed the main action. One drop showed up: a bulk action was harder to find, so we fixed the layout before going wider. We ramped up over a few weeks, and the flag was a one-click way back if things went wrong. The cost was keeping two versions alive for a month, so I set a date to delete the old one and stuck to it.”

Red flag to avoid:

Replacing the screen for everyone in one release with no flag and no way to switch back quickly.

They may ask next:
  • Which numbers would make you stop the rollout and switch back?
  • How do you stop old code behind flags from piling up?
Say it in 60 seconds

Production Incidents 3 questions

Hard Behavioral round Mid-level, Senior Practice question

7. A dashboard that people keep open all day gets slower and slower until the tab crashes. How did you find the leak?

What the interviewer is really testing:
Whether you can find a memory leak with real tools rather than guesses, and whether you know the usual React causes: missing cleanups, subscriptions and caches that only grow.
Answer frame:

Reproduce: a repeatable action, such as switching tabs fifty times, while watching memory.

Measure: heap snapshots before and after, compared to see what keeps growing.

Cause and fix: listeners, sockets or chart instances never cleaned up, or an unbounded cache.

Sample spoken answer:

“Traders kept our dashboard open for a full day, and by afternoon it was sluggish. I reproduced it by switching between two views about fifty times and watching memory climb without coming back down. I took a heap snapshot before and after and compared them. There were thousands of detached chart elements still held in memory. Our chart wrapper created a chart instance in an effect but never destroyed it in the cleanup, and it also added a window resize listener that was never removed. Adding a proper cleanup fixed most of it. I also found a client-side cache of price ticks with no size limit, so I capped it. Memory then stayed flat over a full day of switching, which I checked with a long automated run.”

Red flag to avoid:

Saying you'd just ask users to refresh the page, or guessing at causes without ever taking a heap snapshot.

They may ask next:
  • What does a detached DOM node in a heap snapshot tell you?
  • Why can a missing effect cleanup still leak even after the component unmounts?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

8. How did you set up error reporting for a React app you owned, so you heard about crashes before users complained?

What the interviewer is really testing:
Whether you know what error boundaries do and don't catch in production, and how to make reports readable and alerts useful.
Answer frame:

Catch: error boundaries per screen area report render errors; global handlers catch async and event errors.

Readable: upload source maps and tag each report with the release version.

Useful: alert on new errors or spikes after a deploy, not on every single event.

Sample spoken answer:

“I put error boundaries around each main area, so a broken widget shows a small fallback instead of taking down the page, and each boundary reports the error with the component stack. Boundaries don't see errors in event handlers or rejected promises, so I also hooked the global error and unhandled rejection events. We upload source maps on each build but don't serve them publicly, and every report carries the release version, so after a deploy we can see at once whether a new error came with it. Alerts go to our channel only for new error types or a sudden spike, because an alert on every event got muted within a week. The first real catch was a crash on one old browser, found hours before support heard about it.”

Red flag to avoid:

Thinking one error boundary at the root catches everything, including async and event-handler errors.

They may ask next:
  • How do you let users recover from a boundary's fallback without a full page reload?
  • How do you keep noise from browser extensions out of your error reports?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

9. After adding your team's shared component package to an app, every component from it crashes with an invalid hook call error. What's the likely cause and fix?

What the interviewer is really testing:
Whether you know that two copies of React on one page break hooks, and how to package a shared library so it uses the app's React.
Answer frame:

Cause: the package bundled or installed its own copy of React, so its hooks talk to a different React.

Check: list installed React versions and look inside the package's build output.

Fix: make React a peer dependency, don't bundle it, and dedupe in the app.

Sample spoken answer:

“This one bit us when we split our design system into its own package. Every hook inside it threw the invalid hook call error, even though the code looked fine. Running a list of installed packages showed two copies of React: the app's, and one the design system had installed because React was listed as a normal dependency. Hooks rely on the same React instance that renders the component, so two copies breaks them. I moved React to a peer dependency in the package, marked it as external in the library build so it wasn't bundled, and added a check in CI that fails if more than one React is installed. The trade-off is that apps must provide a compatible React version, which we documented.”

Red flag to avoid:

Rewriting the components as classes to avoid hooks instead of finding the duplicate React.

They may ask next:
  • What are the other common causes of an invalid hook call error?
  • How would you test a shared package against the React version each app uses?
Say it in 60 seconds

Rendering and SSR 1 question

Hard Technical round Mid-level, Senior Practice question

10. When you brought server components into an existing app, how did you decide which parts stay client components, and what caught you out?

What the interviewer is really testing:
Whether you understand the server and client boundary in practice: what each side can do, what can cross it, and where the gains really are.
Answer frame:

Server by default: data fetching and static, heavy markup stay on the server and ship no component code.

Client where needed: anything with state, effects, event handlers or browser APIs, marked with 'use client'.

Boundary rules: props crossing it must be serialisable, and server parts pass into client parts as children.

Sample spoken answer:

“On our product pages I kept the data loading and the long description rendering as server components, so that code never shipped to the browser. The image gallery, the add-to-cart button and the reviews filter needed state or click handlers, so they became small client components, and I pushed the 'use client' line as far down the tree as I could, because everything a client component imports becomes client code too. What caught us out first was passing a click handler and an instance of one of our own classes as props into a client component; props crossing that boundary have to be serialisable. The second was a client layout that imported a server component. That quietly turns it into client code, and it broke because it read from the database, so we passed it in as children instead. The first-load bundle shrank noticeably on those pages.”

Red flag to avoid:

Marking every file 'use client' to make errors go away, or thinking server components are just another name for server-side rendering.

They may ask next:
  • Why does putting 'use client' high up in the tree undo most of the benefit?
  • How do you keep secret keys used in server components from leaking to the client?
Say it in 60 seconds

Component Design 3 questions

Hard System design round Mid-level, Senior Practice question

11. You built a component that several teams use, like a data table or a modal. How did you design its API so it didn't end up with fifty props?

What the interviewer is really testing:
Whether you design reusable components with composition and clear control points, rather than adding a boolean prop for every request.
Answer frame:

Composition: children, slots or compound parts instead of a flag for every variation.

Control: work uncontrolled by default, but accept a value and onChange when a team needs control.

Escape hatches: forward refs, pass through extra props, and keep accessibility built in.

Sample spoken answer:

“Our shared table started as one component with props like showFooter, stickyHeader and hideSortOnMobile, and every team asked for another flag. When I redesigned it, I made it compound: Table, Table.Header, Table.Row and so on, so teams arrange the pieces instead of toggling flags. Sorting and selection work on their own by default, but a team can pass the sort value and a change handler when they need to keep it in the URL. I forwarded refs, passed extra props through to the real elements, and kept keyboard support inside so nobody could break it. The trade-off is that composition is more typing for simple cases, so I also shipped a small preset for the common list page. New requests dropped a lot after that.”

Red flag to avoid:

Adding one more boolean prop for every request until the component can't be understood or tested.

They may ask next:
  • How do you change a shared component's API without breaking every team at once?
  • When is a render prop or a hook a better fit than a compound component?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

12. You had to add several languages, including a right-to-left one, to a React app you owned. What did you change in the code and the layout?

What the interviewer is really testing:
Whether you know the real work in translating an app: message formats and plurals, locale-aware formatting, loading translations and mirroring layouts.
Answer frame:

Strings: move text into message files with placeholders and plural rules, never glue sentences together.

Formatting: dates, numbers and lists through the built-in Intl APIs with the user's locale.

Layout: set dir on the page and use logical CSS properties so right-to-left mirrors itself.

Sample spoken answer:

“Our app had English text hard-coded everywhere, including sentences built by joining pieces, which falls apart in languages with a different word order. I moved every string into message files with named placeholders and proper plural rules, because some languages have more than two plural forms. Dates and numbers went through the Intl APIs with the user's locale instead of our own helpers. Each language's messages load lazily, so users only download their own. For Arabic, I set the dir attribute on the html element and swapped left and right margins for logical properties like margin-inline-start, so most of the layout mirrored without extra CSS. A few icons, like back arrows, had to be flipped by hand. The biggest cost was the one-time string extraction, which we split by screen over a few sprints.”

Red flag to avoid:

Building sentences by joining translated fragments, or treating right-to-left as just aligning text to the right.

They may ask next:
  • How do you stop new hard-coded strings getting merged after the migration?
  • How would you handle text that is much longer in one language and breaks the layout?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

13. You own a long multi-step form with validation that became slow to type in and full of bugs. How did you redesign its state?

What the interviewer is really testing:
Whether you can design form state for scale: where values live, when validation runs and how steps share data, not just wire up inputs.
Answer frame:

Diagnose: one big state object updated on every keystroke re-rendered the whole form.

Redesign: field-level subscriptions or uncontrolled inputs, and one validation schema per step.

Flow: save each step as it's completed, and validate on blur or step change, not every key.

Sample spoken answer:

“Our onboarding form had forty fields over five steps, all in one useState object at the top, with validation running on every keystroke. Typing lagged on older laptops, and step two sometimes lost data when people went back. I moved it to a form library that keeps inputs uncontrolled and only re-renders the field that changed, and I wrote one validation schema per step that runs on blur and when moving on. Each finished step is saved to a draft on the server, so going back just reads the draft and a refresh doesn't lose work. Typing became smooth and the lost-data bugs stopped. The trade-off was learning a new library and rewriting some custom inputs to register with it.”

Red flag to avoid:

Adding debounce to every input to hide the lag without fixing why the whole form re-renders.

They may ask next:
  • How do you validate a field that depends on another step's answer?
  • When would you still choose fully controlled inputs?
Say it in 60 seconds

Performance 4 questions

Hard Technical round Mid-level, Senior Practice question

14. Your busiest page has a poor Interaction to Next Paint score. Where do you look first, and what kinds of fixes have worked for you?

What the interviewer is really testing:
Whether you know that slow interactions come from long tasks on the main thread, and can find which work runs after a click instead of memoising at random.
Answer frame:

Find it: field data to learn which interactions are slow, then a performance trace to see the long task.

Common causes: a big re-render after the click, heavy handler work, or third-party scripts.

Fixes: smaller updates, mark slow updates as transitions, split or defer heavy work.

Sample spoken answer:

“Our field data showed the worst interaction was clicking a filter chip on the search page. In a performance trace, one click caused a long task where most of the time was React re-rendering the whole results area, plus an analytics call that serialised a big object synchronously. I moved the filter state down so only the results list re-rendered, wrapped the results update in startTransition so the chip itself responded straight away, and pushed the analytics work to run after the next paint. The chip now feels instant even on slow phones. The trade-off is that for a moment the old results stay on screen, so I dimmed them to show the update was in progress. I checked the gain in field data over the next two weeks, not just on my laptop.”

Red flag to avoid:

Jumping straight to wrapping everything in memo without measuring which interaction is slow or why.

They may ask next:
  • Why can a page score well in the lab and still be slow for real users?
  • What does startTransition actually change about how React schedules the update?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

15. One app-wide context holds the user, theme and cart, and every cart change re-renders almost the whole tree. How did you restructure it?

What the interviewer is really testing:
Whether you know that every consumer re-renders when a provider's value changes, and the practical ways to shrink that blast radius.
Answer frame:

Why: every consumer re-renders when the value changes, and memo on the child doesn't stop it.

Split: separate contexts for things that change at different speeds, and state from actions.

Stabilise: memoise the value object; use a store with selectors if one value changes very often.

Sample spoken answer:

“We had one AppContext with user, theme and cart together, and adding an item to the cart re-rendered the header, the sidebar and every product card. The context value changed on every cart update, and every component reading that context re-rendered, whatever part it used. I split it into three contexts, and for the cart I also split state from actions, so buttons that only add items read the actions context, which never changes. I memoised the value objects so a parent re-render didn't create new ones. Adding to the cart then touched only the cart badge and the cart page. The trade-off is more providers to wire up, so I wrapped them in one AppProviders component.”

Code:
const CartItemsContext = createContext([]);
const CartActionsContext = createContext(null);

function CartProvider({ children }) {
  const [items, setItems] = useState([]);
  const actions = useMemo(() => ({
    add: (item) => setItems((xs) => [...xs, item]),
    remove: (id) => setItems((xs) => xs.filter((x) => x.id !== id)),
  }), []);
  return (
    <CartActionsContext.Provider value={actions}>
      <CartItemsContext.Provider value={items}>{children}</CartItemsContext.Provider>
    </CartActionsContext.Provider>
  );
}
Red flag to avoid:

Believing React.memo on the child components will stop re-renders caused by a context change.

They may ask next:
  • Why doesn't wrapping a consumer in React.memo stop the context re-render?
  • At what point would you move this state into an external store instead?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

16. Tell me about a time your main JavaScript bundle grew a lot over a few months. How did you find what grew, and how did you stop it from happening again?

What the interviewer is really testing:
Whether you use a bundle analyser to find real causes, and whether you put a guard in place so the problem doesn't quietly come back.
Answer frame:

Find: a bundle analyser to see which packages and files take the space.

Common culprits: a whole library imported for one function, locale data, barrel files, heavy code on the first route.

Guard: a size budget checked on every pull request.

Sample spoken answer:

“Our first-load bundle had roughly doubled in six months and the landing page felt slow on mobile. I ran a bundle analyser and found three things. A date library was pulling in every locale, one icon import went through a barrel file that dragged in the whole icon set, and a rich-text editor used on one admin page was sitting in the main chunk. I switched to importing only the locales we used, imported icons one by one, and lazy-loaded the editor route. The main bundle came down to roughly where it had started. Then I added a size check to CI that comments on the pull request and fails it past a budget, so the next growth gets a conversation instead of a surprise.”

Red flag to avoid:

Fixing the size once with no budget or check, so it creeps back within a few months.

They may ask next:
  • How do you decide what belongs in the first chunk and what can load later?
  • What would you do when a new feature genuinely needs a large library?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

17. A live dashboard gets hundreds of updates a second over a WebSocket, and the page stutters. How did you design the updates so the UI keeps up?

What the interviewer is really testing:
Whether you know that one state update per message overwhelms rendering, and can batch, throttle and narrow updates in a way you can explain.
Answer frame:

Buffer: collect incoming messages outside React state, for example in a ref or a small store.

Flush: apply them in one state update per animation frame or on a short timer.

Narrow: let each row subscribe to its own data, and virtualise long lists.

Sample spoken answer:

“Our trading screen called setState for every message, so at peak we asked React to render hundreds of times a second and the whole page stuttered. I changed it so messages go into a buffer held in a ref, keyed by instrument so a newer price simply replaces an older one. Once per animation frame I flush the buffer into state in a single update. That capped rendering at the screen's frame rate and threw away prices nobody would ever have seen. I also memoised the rows so only rows whose price changed re-render, and virtualised the list so we only draw what's on screen. The trade-off is a delay of up to one frame, which nobody can notice, and it meant the rows needed stable keys and props.”

Code:
function useBufferedPrices(socket) {
  const [prices, setPrices] = useState({});
  const buffer = useRef({});
  useEffect(() => {
    let frame = 0;
    const flush = () => {
      frame = 0;
      const batch = buffer.current;
      buffer.current = {};
      setPrices((prev) => ({ ...prev, ...batch }));
    };
    const onMessage = (event) => {
      const { symbol, price } = JSON.parse(event.data);
      buffer.current[symbol] = price; // newer price replaces older
      if (!frame) frame = requestAnimationFrame(flush);
    };
    socket.addEventListener('message', onMessage);
    return () => {
      socket.removeEventListener('message', onMessage);
      cancelAnimationFrame(frame);
    };
  }, [socket]);
  return prices;
}
Red flag to avoid:

Keeping one state update per message and hoping memo alone will make it fast.

They may ask next:
  • What happens to this approach when the tab is in the background?
  • When would you move the prices into an external store with useSyncExternalStore instead?
Say it in 60 seconds

Testing 1 question

Medium Technical round Mid-level, Senior Practice question

18. For a React app you owned, what mix of unit, integration and end-to-end tests did you settle on, and what did you decide to stop testing?

What the interviewer is really testing:
Whether your testing strategy is a deliberate trade between confidence, speed and upkeep, not just a coverage number.
Answer frame:

Weight: most tests render a screen with a mocked network and act like a user.

Edges: plain unit tests for tricky logic, a few end-to-end tests for money and login paths.

Stopped: snapshot tests and tests of internal state that broke on every refactor.

Sample spoken answer:

“Where I landed was mostly integration-style component tests. We render a whole screen, mock the network at the request level, and click and type like a user would. Pure logic, like price rules and date handling, gets plain unit tests because they're fast and exact. On top of that we kept about a dozen end-to-end tests for sign-in, checkout and anything that touches money, running against a staging build. What I stopped was large snapshot tests, because people just updated them without reading, and tests that checked a component's internal state, which broke every time we refactored without catching real bugs. The trade-off is that integration tests are slower than unit tests, so we run them in parallel in CI.”

Red flag to avoid:

Quoting a coverage target as the whole strategy, with no view on which tests actually catch bugs.

They may ask next:
  • Where do you mock the network, and why there instead of mocking your own fetch functions?
  • How do you keep end-to-end tests from becoming slow and flaky?
Say it in 60 seconds

Reviews and Decisions 4 questions

Medium Situational round Mid-level, Senior Practice question

19. A junior developer's pull request copies props into state and uses several useEffects to keep them in sync. How do you review it and help them learn, without rewriting it for them?

What the interviewer is really testing:
Whether you can explain the better pattern clearly and coach someone, rather than just blocking the PR or taking over the work.
Answer frame:

Name the bug risk: synced state goes out of date and causes an extra render with the old value first.

Point the way: derive values during render, or reset with a key; one worked example, not a rewrite.

Coach: a short call, and let them make the change and own it.

Sample spoken answer:

“I'd start by reading it for what works, then leave one clear comment on the pattern rather than ten small ones. I'd explain that copying a prop into state means the two can disagree, and each effect adds a render where the screen briefly shows the old value. I'd show a tiny example of computing the value during render instead, and mention resetting a component with a key when the whole form should start fresh for a new record. Then I'd suggest a ten-minute call, because this is easier to explain live than in comments. I'd let them make the change so they own it. Last time I did this, the same developer caught the pattern in someone else's review a month later.”

Red flag to avoid:

Rewriting the code yourself and merging it, so the junior learns nothing and stops asking questions.

They may ask next:
  • When is copying a prop into state actually correct?
  • How do you keep review comments from feeling like a lecture to a junior?
Say it in 60 seconds
Hard Situational round Mid-level, Senior Practice question

20. Your lead wants to move the whole app to a new framework with server components this quarter, alongside the normal roadmap. You think it's too risky. What do you do?

What the interviewer is really testing:
Whether you can push back on a big risky plan with evidence and a safer alternative, instead of just agreeing or just refusing.
Answer frame:

Understand: what problem the move is meant to solve, such as slow first load or SEO.

Evidence: a spike on one real page to measure the gain and list what breaks.

Alternative: move one route at a time, with a clear point where you stop or carry on.

Sample spoken answer:

“I'd first ask what we're trying to fix, because the answer shapes everything. If it's slow first load on marketing pages, that's a much smaller job than moving the logged-in app. Then I'd offer a time-boxed spike: move one real page, measure load times, and list what broke, like libraries that only work in the browser, our auth handling and our testing setup. I'd bring that back with an estimate and a proposal to migrate route by route, starting with public pages, with a checkpoint where we decide to continue or stop. I've done this once, and the spike showed the logged-in screens gained very little, so we moved only the public pages and saved the team a quarter.”

Red flag to avoid:

Either agreeing to a big-bang rewrite with no evidence, or refusing flatly without offering a way to test the idea.

They may ask next:
  • What would change your mind and make you support the full move?
  • How do you run two frameworks side by side during a gradual migration?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

21. In code review, a teammate renders user comments as HTML with dangerouslySetInnerHTML so that bold and links show up. What do you say, and what do you suggest instead?

What the interviewer is really testing:
Whether you recognise a cross-site scripting risk, block it firmly but helpfully, and know the safer ways to show rich user content.
Answer frame:

Risk: raw user HTML can run scripts through attributes like onerror, a stored XSS hole.

Safer options: render a limited format like Markdown to React elements, or sanitise HTML with a proven library.

Defence in depth: sanitise on the server too, and add a Content Security Policy.

Sample spoken answer:

“I'd block that pull request, but kindly, and explain why in one comment. React escapes text by default, and dangerouslySetInnerHTML switches that off, so a comment containing an image tag with an onerror handler would run script for every reader, which could steal sessions. Then I'd offer a way forward, because the need is real. My first choice is to store comments as Markdown and render them to React elements, allowing only bold, italics and links. If we truly need HTML, run it through a well-maintained sanitiser with a short allow-list, ideally on the server when saving as well as when rendering. I'd also check links can't use javascript URLs. Last time this came up, we added a lint rule that flags any new dangerouslySetInnerHTML for a second reviewer.”

Red flag to avoid:

Approving it because the comments come from logged-in users, or trying to strip script tags with a regular expression.

They may ask next:
  • Why is sanitising only in the browser not enough?
  • What would a Content Security Policy stop here, and what wouldn't it?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

22. Tell me about a technical decision you made in a React codebase that you'd reverse now. What did it cost, and what did you learn?

What the interviewer is really testing:
Whether you can own a mistake honestly, explain why it looked right at the time, and show what you do differently now.
Answer frame:

Decision: what you chose and why it made sense with what you knew then.

Cost: the concrete pain it caused the team or users.

Change: how you fixed it or now decide differently.

Sample spoken answer:

“Early in a project I pushed for a CSS-in-JS library that built styles at runtime, because it made theming easy and we liked keeping styles next to components. It worked well at first. As the app grew, profiling showed style work was a real part of our render time on big lists, and moving to server rendering was harder because of how it injected styles. We spent a good part of a quarter moving to a zero-runtime approach. What I learned is to test a choice at the scale we expect in a year, not the size we are today, and to write down why we chose something so the next person can tell when those reasons no longer hold. I now write a short decision note for anything hard to undo.”

Red flag to avoid:

Picking a trivial mistake, or blaming the decision entirely on someone else.

They may ask next:
  • How did you convince the team to spend time undoing it?
  • What decision are you most unsure about in your current codebase?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

You practiced these. On the real call, ClapAssist helps with the rest.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your live interview audio and screen are never stored. Your resume and notes are saved to your account so the app fills them in on any computer. It stays out of screen share on every plan, including Free; only you can see it.

Download with 10 free minutes
Mac and Windows · Stays out of screen share · No card