Frontend Architecture Interview Questions

Architecture rounds ask how you would structure a codebase several teams have to share, and what you would trade away to get there.

This topic has 23 curated Frontend Architecture questions covering module boundaries, coupling and cohesion, design systems, migrations and error strategy. Below are 8 of them in full, with the same answers you get in the interactive tool.

Practise all 23 Frontend Architecture questions →

Difficulty breakdown

  • Easy3
  • Medium6
  • Hard10
  • Expert4

Question formats

  • Architecture6
  • Multiple choice4
  • Best practice3
  • Concept3
  • Scenario3
  • Code review2

What these questions cover

  • structure
  • design-systems
  • errors
  • migration
  • tooling
  • ux
  • api
  • ci
  • collaboration
  • delivery

8 example Frontend Architecture interview questions

ExpertArchitecture

A four-year-old app must move to a new router and rendering model with no big-bang release. Design the migration

Route-by-route, behind a proxy or a routing shim that can send any given path to the old or the new implementation, with the choice controlled by a flag you can flip per route and per cohort. Start with a low-traffic, low-risk route to prove the seams — shared session and auth, shared design tokens, and navigation between the two halves that does not feel like leaving the app. Extract the genuinely shared pieces (auth, API client, design system) into their own boundary first, since both halves depend on them and they are what make dual-running possible. Define a completion criterion and a deadline for deleting the old path, and track the percentage migrated as a visible number.

The failure mode is not technical; it is the permanent half-migration, where the interesting routes move, the boring ones do not, and the team maintains two stacks forever. That is why the deletion deadline and the visible progress metric belong in the design rather than in a follow-up ticket. The other hard constraints are the ones that span both halves: session and auth must be shared or users get logged out crossing the boundary, the two halves will ship duplicate framework code until the old one dies so the bundle gets temporarily worse, and cross-boundary navigation is a full page load unless you plan for it.

MediumBest practice

What states must every data-driven view handle, and which one is most often forgotten?

At minimum: loading (first load, and separately refreshing with data already on screen), empty (the request succeeded and there is genuinely nothing), error (with a distinction between retryable and not), and success. Partial is a fifth where some data arrived and some failed. The one most often forgotten is EMPTY — teams ship a view that renders an empty list identically to a failed request, so the user cannot tell "you have no orders" from "we could not load your orders", and the two require completely different actions. Second most forgotten is refreshing-with-data, where showing a spinner over existing content is worse than leaving it visible. Making these states part of the component contract, rather than ad-hoc conditionals, is what keeps them from being skipped.

Empty and error rendering identically is a genuine product bug rather than a polish issue: the two require opposite actions from the user, and a blank list quietly tells them the wrong one. The refreshing-with-data state is the other one worth naming, because the naive implementation replaces visible content with a spinner on every poll, which reads as the app losing data. Putting these in the component contract is what stops them being rediscovered per feature.

HardCode review

Review this hook that several pages depend on

export function useApp() {
  const user = useUser();
  const cart = useCart();
  const flags = useFlags();
  const theme = useTheme();
  const notifications = useNotifications();
  return { user, cart, flags, theme, notifications };
}

Every consumer subscribes to everything. A notification arriving re-renders a page that only wanted the theme, and since the hook returns a new object each call, memoization downstream is defeated too. It also creates a dependency cycle risk and makes testing painful, because rendering anything requires providing all five. The fix is to let consumers use the specific hooks they need, which is both clearer and narrower; if the convenience wrapper is genuinely wanted, make it selector-based so a caller subscribes to one slice. Architecturally this is a coupling smell: a module that everything imports becomes a module nothing can change.

Convenience aggregators like this are common and quietly responsible for app-wide re-render storms.

EasyConcept

Why is "data down, events up" the default direction of flow in component architecture?

Because it makes the source of truth findable. When data flows down from an owner and changes flow back up as events, there is exactly one place that can change a given value, and you can trace any piece of UI back to it by reading upwards. Break the rule — a child mutating a shared object, or two components each holding a copy — and the state has several possible writers, so debugging becomes a search rather than a read. It also makes components reusable: a component that receives data and emits events makes no assumption about where it sits, whereas one that reaches into a store is tied to that store. The practical payoff is that "where does this value come from" always has a single answer.

The practical payoff is that "where does this value come from" always has one answer you can find by reading upwards, instead of a search for every possible writer. That is also why the rule is worth stating as a default rather than a law — it is occasionally right to break it, and the cost of breaking it is exactly that traceability. The reusability consequence is the less obvious half: a component that reaches into a store has quietly declared where it is allowed to be used.

MediumFollow-up

You extracted a shared Button into the design system. What happens the first time a team needs it to behave differently?

That request is the test of whether the abstraction was right. Ask what the difference actually is: a new visual variant is usually legitimate and belongs in the system as a named option; a one-off behavioural change usually is not, and adding a prop for it starts the slide towards the twelve-boolean component. Options in order: satisfy it with an existing variant; add a genuinely general variant; let the caller compose around the button rather than configure it; or, if the need is truly specific, let that team build their own and accept the duplication. The worst outcome is a `specialCase` prop, because it encodes one team’s requirement into everybody’s component forever.

A natural interviewer follow-up that probes whether the candidate can say no to a reasonable-sounding request for a good reason.

MediumMultiple choice

Why do feature-based folders usually scale better than type-based ones?

Grouping by type (`components/`, `hooks/`, `utils/`) means every feature is smeared across the tree and every change is a multi-folder diff. Grouping by feature keeps related code together and makes deletion possible — you can remove a feature by removing a folder.

MediumScenario

Two teams share one repo and constantly block each other: a broken build stops everyone, and every PR touches shared files. What do you change?

Separate the concerns rather than the repo, at least first. Give each team clear ownership boundaries with codeowners so reviews route correctly. Split the shared files people keep colliding in — a single barrel export or a global route table is a guaranteed conflict point; per-feature registration removes it. Make CI run only what a change affects so one team's failing test does not block the other's merge, and make the build fail fast per package. Then, if the pain is genuinely build time rather than coordination, consider workspaces so each app builds independently.

The instinct is to split the repo, and it usually trades a coordination problem for a versioning one — now the teams are blocked on publishing and consuming each other's packages instead, with slower feedback. Most of this pain is caused by a handful of shared files and an all-or-nothing CI pipeline, both of which are fixable without moving any code. Worth measuring which it actually is before restructuring: count the conflicts and find out what fraction of blocked time is build versus review.

HardArchitecture

How would you design loading and error handling so it is consistent across a large app?

Decide the taxonomy first, because "error" is not one thing: a network failure is retryable, a 403 is not, a 404 is a different page, a validation error belongs next to a field, and an unexpected exception is a bug. Each deserves distinct UI. Then put the handling at boundaries rather than in every component — a route-level error boundary plus a suspense boundary gives a default for anything below it, and components opt into finer-grained states only where the UX warrants. Standardise the shape data hooks return (`idle | loading | success | error` with a typed error) so every consumer branches the same way, and provide shared components for the common presentations. Critically, every error path must be reachable in development and in tests, or it will be written once and never exercised until a user finds it.

The taxonomy is what prevents the usual outcome — one generic "Something went wrong" that tells nobody anything and offers no action.

The remaining 15 Frontend Architecture questions — plus mock interviews, spaced revision and progress tracking — are in the free interview prep workspace.

Related interview topics