Skip to main content

Vibe coder or product engineer frontend interview questions

9 questions on frontend for vibe coder or product engineer candidates. Expect prompts such as “React Server Components in Next.js App Router: tradeoffs” and “When to use ISR versus SSR versus SSG in Next.js”, each with a worked answer outline and the follow-ups interviewers push on.

Questions

Showing 1 to 9 of 9 frontend questions.

As asked

You are building a product dashboard in Next.js 14 with the App Router. Walk me through how you decide which components to keep as Server Components versus mark with 'use client', and what concrete problems arise when you get that boundary wrong.

Sample answer outline

Server Components run only on the server, so they can directly access databases and secrets without exposing them to the client bundle, but they cannot hold state or attach event listeners. The boundary mistake that bites most people is importing a 'use client' component from a Server Component that passes non-serializable props like functions or class instances, which throws a serialization error at runtime. A strong answer mentions that context providers must be Client Components, that you can pass Server Component output as children to a Client Component wrapper, and that mixing them carelessly inflates the JS bundle with code that did not need to ship.

Expect these follow-ups

  • How does streaming with Suspense change your data-fetching strategy in the App Router?
  • If a Server Component needs to share state with a Client Component, what are your options?
nextjsreactserver-componentsapp-routerfrontend

As asked

You have a product with a public-facing blog, a user dashboard, and a search page. Walk me through which rendering strategy you pick for each and why, and what happens when ISR serves stale content at the wrong moment.

Sample answer outline

Static generation at build time makes sense for the blog if posts do not change more than a few times a day and you want the best TTFB. ISR adds revalidation on a time interval so stale content is at most N seconds old, which is fine for a marketing blog but wrong for a page that shows live inventory or real-time data. The user dashboard must be SSR or fully client-rendered because the content is user-specific and cannot be cached at the CDN. Search should be client-rendered with an API call since the query is dynamic. The ISR stale-content failure mode is a revalidation that happens in the background: the user might see a deleted post for up to the revalidation interval, which you mitigate with on-demand revalidation triggered by a CMS webhook.

Expect these follow-ups

  • How do you trigger on-demand revalidation for a specific path in Next.js when a CMS publishes a new post?
nextjsisrssrssgrendering-strategies

As asked

You have an async data-fetch in a React app that can be in a loading state, a success state with data, or an error state. How do you model this in TypeScript so the compiler prevents you from accessing data when you are in the error state?

Sample answer outline

A discriminated union with a shared literal type field like status: 'loading' | 'success' | 'error' lets TypeScript narrow the type inside conditional branches. When you check status === 'success' the compiler knows data is present; in the 'error' branch it knows error is present. This beats an object with all fields optional because optional fields let you access data in the error branch without a type error at compile time. A strong answer also mentions using a library like neverthrow or zod to parse API responses and produce these union types at the boundary rather than casting.

Expect these follow-ups

  • How would you extend this pattern to handle paginated responses or optimistic UI updates?
typescriptdiscriminated-unionsreacttype-safetyfrontend

As asked

A user clicks a like button and you want the count to update instantly before the API call completes. Walk me through how you implement this optimistic update and handle the case where the API call fails, including what happens if the user clicks again before the first call resolves.

Sample answer outline

You update local state immediately with the expected result, fire the API call in the background, and on error revert to the previous state and surface a toast. The concurrency case is the hard part: if the user clicks twice rapidly you need to either debounce the action, queue mutations in order, or use a cancel token to abort the in-flight request before firing the next one. With React Query or SWR, the onMutate callback captures the previous data for rollback and the onError callback restores it. A strong answer also covers idempotency on the server so duplicate requests do not double-apply the action.

Expect these follow-ups

  • How does React Query's useMutation differ from writing this logic manually with useState and useEffect?
optimistic-uireactreact-queryconcurrencyfrontend

As asked

Your codebase has grown to 40 components all built with Tailwind. Class strings are getting unwieldy and variant logic is scattered. What patterns do you use to keep the code readable and the variants consistent without abandoning Tailwind?

Sample answer outline

The most maintainable approach is a variant utility like cva (class-variance-authority) or Tailwind Variants, which lets you declare base classes and named variant slots and compose them with TypeScript types. This replaces ad hoc template literal ternaries with a schema. You also want to run prettier-plugin-tailwindcss to enforce class ordering so diffs are readable. A strong answer mentions that extracting a component means you stop repeating the class string at every call site, and that you should avoid the @apply escape hatch in CSS files because it hides what styles a component has and breaks the co-location benefit of Tailwind.

Expect these follow-ups

  • How do you handle dark mode variants across a design system using Tailwind?
tailwindcsscomponent-librarydesign-systemfrontend

As asked

Your product's landing page scores 35 on PageSpeed Insights. LCP is 4.2 seconds and CLS is 0.28. Walk me through a systematic approach to diagnosing and fixing both without a complete redesign.

Sample answer outline

For LCP, the first thing to check is whether the largest element is an image with no priority prop or fetchpriority=high, causing it to be discovered late. Next.js Image with priority fixes this by preloading the LCP image. Also check if the LCP element is behind a client-side render, because the browser's LCP timer fires when the element paints. For CLS, the cause is almost always images or embeds without explicit width and height causing layout shifts as they load, or fonts causing FOIT/FOUT. Reserving space with aspect-ratio CSS or explicit dimensions on the Next.js Image component eliminates most CLS. A strong answer also mentions using the Web Vitals Chrome extension to click the CLS debug overlay and identify exactly which elements are shifting.

Expect these follow-ups

  • How does Next.js font optimisation with next/font help CLS, and what does it do under the hood?
web-vitalsperformancenextjsclslcp

As asked

You are using React Query across a product with many related entities. A user updates a post and you want the post list, the post detail, and a sidebar count to all update immediately. How do you structure your query keys and invalidation so this works without a full page reload?

Sample answer outline

React Query's cache is keyed by arrays, so a hierarchical key like ['posts', postId] or ['posts', 'list', filters] lets you invalidate by prefix with queryClient.invalidateQueries(['posts']). After a mutation succeeds you call invalidateQueries to trigger background refetches for all matching keys. For the sidebar count you either put it under the same prefix so it is swept by the same invalidation, or you update it manually with setQueryData when you know the delta. A strong answer also distinguishes between invalidating (marks stale, refetches in background) versus removing (removes from cache, shows loading on next mount), and notes that you should invalidate on both success and relevant error states.

Expect these follow-ups

  • How would you handle a scenario where two components on the same page share a query but one needs fresher data than the other?
react-querycachingfrontendstate-managementinvalidation

As asked

A product dashboard re-renders its entire component tree on every keystroke in a search input. Walk me through your profiling workflow and the specific fixes you would apply.

Sample answer outline

Start with the React DevTools Profiler to record an interaction and see which components re-rendered and why. The most common causes are a parent holding the search state that also renders unrelated expensive children, inline object or function props that create new references on every render breaking memo, and context values that change on every render. The fix depends on cause: lift the search state down to only the components that need it, wrap expensive pure children in React.memo, stabilise callbacks with useCallback and derived objects with useMemo. A strong answer notes that useMemo and useCallback are not free and should only wrap things that are genuinely expensive or passed to memo children, and that the profiler's 'why did this render' tooltip tells you the exact prop that changed.

Expect these follow-ups

  • When would you reach for a state management library like Zustand instead of solving this with memo and context?
reactperformanceprofilingre-rendersfrontend

As asked

Your product needs to support a white-label mode where partners can change brand colours, typography, and border radii. Walk me through how you structure design tokens and wire them into a Tailwind and React component system.

Sample answer outline

Design tokens live as CSS custom properties on :root or a theme class, so switching themes is a class swap without a JavaScript re-render. In Tailwind you map your semantic token names like --color-primary into tailwind.config.js under theme.extend.colors as CSS variable references. Components reference the semantic tokens not specific hex values, so a partner override only needs to change the token values. You expose a theme config object per partner stored in the database, generate the CSS variable block server-side, and inject it into the document head. A strong answer also covers dark mode by layering a second set of token values under a .dark class, and mentions that token naming should be semantic like color-action-primary not presentational like color-blue-500.

Expect these follow-ups

  • How do you validate that a partner's custom colour choices meet WCAG AA contrast requirements before going live?
design-tokensthemingtailwindwhite-labeldesign-system