Interview Handbook
A visual, comprehensive, and easy-to-remember guide — covering requirement clarification, architecture, rendering, performance, real-time, and security. Includes diagrams, comparison tables, flashcards, and a question bank for self-assessment.
Unlike backend system design (which focuses on scaling servers, databases, and throughput), frontend system design focuses on user experience: client architecture, data flow, rendering, perceived performance, and codebase maintainability.
In an interview, you typically have 45–60 minutes to design a client-side product (e.g., News Feed, Autocomplete, Chat, Photo Gallery). The interviewer evaluates how systematically you think, not how many APIs you remember.
RADIO is a popular mnemonic to help you not miss any steps. Proceed sequentially but be flexible to revisit steps when needed. The RADIO framework is referenced from greatfrontend.com.
| Step | Goal | Suggested duration |
|---|---|---|
| R — Requirements | Understand the problem: functionality, users, constraints, devices. | ~5 minutes |
| A — Architecture | Draw the major components & data flow between them | ~12 minutes |
| D — Data Model | State definition: server data vs. client/UI state | ~8 minutes |
| I — Interface/API | The contract between client and server, and between components (props). | ~8 minutes |
| O — Optimizations | Performance, a11y, network, UX, security — deep dive. | ~12 minutes |
What is the core difference between frontend system design and backend system design?
This is the most important step, yet the most overlooked. Jumping straight into a solution without understanding the problem is the biggest trap in interviews.
What does the product do? Users can: create posts, like, comment, infinite scroll, upload images...
How well does the product perform? Load speed, smoothness, offline capability, a11y, SEO.
Device (mobile/desktop), supported browsers, network (3G?), multilingual support, framework.
Which of the following requirements has the MOST DIRECT and SIGNIFICANT impact on choosing a rendering pattern (CSR/SSR/SSG)?
After understanding the problem, you outline the major "blocks" of the client system and how they communicate. Goal: separation of concerns — each part has a clear responsibility.
Idea: data flows from Server → Network → Store → View Model → View, while events flow in the opposite direction. Each layer can be tested and replaced independently.
Knowledge of data & logic. Calling APIs, managing state, handling events. Not concerned with specific display.
Only receives props and renders UI. No business state. Easy to test, easy to reuse, easy to build Storybook.
Micro-frontend splits a large application into multiple independent parts, each owned by a team (potentially using different frameworks). It is effective for **large organizations with many teams**, but increases complexity in build processes, duplicate bundles, and UX consistency.
The main benefit of separating Container and Presentational components is improved separation of concerns: Container components handle logic, state, and data fetching, while Presentational components focus solely on rendering UI. This makes code more reusable, testable, and easier to maintain.
Core question: Where and when is HTML generated? This choice affects SEO, rendering speed, server cost, and complexity.
| Pattern | When is HTML created? | SEO | FCP speed | Good for |
|---|---|---|---|---|
| Client-Side Rendering (CSR) | In the browser, after JS runs | Weak (requires workaround) | First-time slow | Dashboard, post-login app, minimal SEO needed. |
| SSR (Server-Side Rendering) | For each request, on the server | Good. | Fast (TTFB depends on server) | Personalized page, data changes continuously, requires SEO. |
| SSG (Static Site Generation) | During build, before deployment | Best. | Fastest (CDN) | Blog, documentation, marketing, landing page |
| ISR (Incremental Static Regeneration) | Pre-built + periodic regeneration | Good. | Fast | E-commerce, many pages but updates are not continuous. |
Hydration is the process of attaching JavaScript event handlers to server-generated HTML, turning a "static" page into an "interactive" one. The issue: it can be heavy and delay Time to Interactive (TTI). Techniques to reduce cost:
You are building a news blog that requires good SEO and extremely fast loading, with content updated several times a day. Which pattern is the most suitable?
Performance isn't just about being "fast"—it's about being fast at the moments users perceive. Google measures this through Core Web Vitals.
Largest Contentful Paint. The time it takes for the largest element to appear. Good: < 2.5s
Interaction to Next Paint. Interaction response delay (replaces FID). Good: < 200ms
Cumulative Layout Shift. Layout shift level. Good: < 0.1
Instead of sending all JavaScript at once, only load the necessary parts. This is one of the most effective optimizations for FCP/LCP.
// Split bundle by route — load only when needed
const Dashboard = React.lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<Skeleton />}>
<Dashboard />
</Suspense>
);
}Rendering 10,000 rows at once will kill performance. Virtualization only renders items within the viewport (plus a small buffer), reusing DOM elements when scrolling.
Popular libraries: react-window, react-virtuoso, @tanstack/virtual.
A page displaying a list of 50,000 products lags heavily when scrolling. What is the most optimal solution?
Client-server communication significantly impacts UX. You need to know when to use REST, GraphQL, and how to handle pagination, retry, and race conditions.
| REST | GraphQL | |
|---|---|---|
| Fetch data | Multiple fixed endpoints | 1 endpoint, client declares the required fields themselves. |
| Over/Under-fetching | Prone to (over-fetching/under-fetching) | Minimize as much as possible. |
| Caching | Simple (HTTP cache by URL) | More complex (requires normalization) |
| Versioning | Commonly /v1, /v2 | Schema evolution, deprecate field. |
| "Matches" | Simple API, cache by URL | Complex UI with multiple data relationships. |
?page=2&limit=20. Easy to understand, supports page navigation. Drawback: data inconsistency when new items are inserted.
Stable with changing data, suitable for infinite scroll and feeds. Difficult to jump to arbitrary pages.
UI loads more when scrolling to the end (IntersectionObserver), typically using a cursor below.
When a user types "rea" quickly → "react", the request for "rea" may arrive AFTER "react" and overwrite the correct result. Solution:
let controller;
async function search(query) {
controller?.abort(); // Cancel the old request.
controller = new AbortController();
const res = await fetch(`/api?q=${query}`, {
signal: controller.signal
});
return res.json();
}Combine with debounce (~300ms) to reduce the number of requests, and check "whether this request is still the latest" before calling setState.
In an autocomplete field, how do you ensure the displayed results always match the latest keyword the user has typed?
Incorrect state management is the root cause of most frontend bugs. The key is to classify state types and choose the right tool for each — don't put everything into a single global store.
| Type | Description | The right tool |
|---|---|---|
| Server State | List of articles, user profiles (requires caching, sync, refetch) | React Query, SWR, RTK Query, Apollo |
| Local State | Input value, open/close dropdown | useState, useReducer |
| Global UI State | Theme, login, cart, global modal | Context, Zustand, Redux, Jotai |
| URL State | Selected tab, filters, search keywords | Router (query parameters) |
One-way data flow makes debugging easier: every state change goes through action → reducer, so we always know "why the state changed."
A list of blog posts is fetched from the API and needs caching, auto-refetch when stale, and request deduplication. Which state type and tool should you use?
A good component is like a good function: easy to use correctly, hard to misuse. In interviews, designing a "props API" for a component (such as Autocomplete, Modal) is a very common question.
interface AutocompleteProps<T> {
// Data & How to Retrieve It
fetchOptions: (query: string) => Promise<T[]>;
// Custom display
renderOption?: (item: T) => ReactNode;
getOptionLabel: (item: T) => string;
// Behavior
debounceMs?: number; // default 300
minChars?: number; // default 1
// Controlled
value?: T | null;
onChange?: (item: T | null) => void;
// State
loading?: boolean;
emptyMessage?: string;
}Allows users to compose child components flexibly, sharing state implicitly via Context.
<Tabs defaultValue="profile">
<Tabs.List>
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Panel value="profile">...</Tabs.Panel>
<Tabs.Panel value="settings">...</Tabs.Panel>
</Tabs>When designing an Autocomplete component for an interview, which of the following factors is OFTEN FORGOTTEN but highly valued?
Proper caching helps apps run faster and remain functional even on weak networks. However, "there are only two hard problems in computer science: naming things and cache invalidation."
| The mechanism | Capacity | Characteristics | Used for |
|---|---|---|---|
| **Memory (JS)** | RAM | Lost when reloading | Cache runtime, state |
| localStorage | 5–10 MB | Synchronous, string only, long-lived. | Theme, small token, prefs |
| sessionStorage | ~5MB | Lost when closing the tab | Session-based temporary data |
| IndexedDB | Large (hundreds of MB+) | Asynchronous, store object | Offline data, big data |
| Cookies | ~4KB | Attach to every request | Session ID (HttpOnly), auth |
| Cache API | Large | Through Service Worker | Cache response, PWA offline |
Stale-while-revalidate (core of SWR/React Query): immediately returns stale data to the UI for smoothness, while fetching in the background to update → users see fast results and always have fresh data.
You need to store several hundred MB of data for the app to work offline. Which mechanism is suitable?
Chat, notifications, stock prices, "typing..." — when to use polling, SSE, or WebSocket? Each has its own trade-offs in latency, data direction, and complexity.
| The mechanism | Afternoon | Complexity | Good for |
|---|---|---|---|
| Short Polling | The client asked: | Low | No rush to update, prototype. |
| Long Polling | Client asks (keep connection) | Average | Fallback when WebSocket is unavailable |
| SSE | Server → Client (one-way) | Low–Medium | Notification, feed, log stream |
| WebSocket | two-way | Cao | Chat, collaboration, gaming, trading |
When liking a post, update the UI immediately before the server responds, then rollback if an error occurs. The user experiences instant feedback even with network latency.
You are building a 1-on-1 chat feature with "typing..." indicators and instant two-way messaging. What is the most suitable mechanism?
A good system serves all users — including those using screen readers, keyboards, and people from different languages/cultures. This is what distinguishes an average candidate from an excellent one.
All operations can be performed using the keyboard. Focus is clearly visible, tab order is logical, and there is no focus trap.
Use correct semantic tags (button, nav, main) before considering ARIA. Proper HTML equals free accessibility.
aria-label, role, aria-live for dynamic content. Only use ARIA when HTML is insufficient.
Color contrast meets WCAG (4.5:1), respects prefers-reduced-motion.
<button> already has built-in focus, keyboard, and role — no need for <div onClick> then patching with ARIA.new Intl.NumberFormat('vi-VN', {
style: 'currency', currency: 'VND'
}).format(1500000); // "1.500.000 ₫"What is the best way to create an accessible button?
Frontend cannot fully protect itself (everything on the client side can be spoofed), but there are vulnerabilities that FE engineers must guard against: XSS, CSRF, and sensitive information exposure.
| Attack | The essence | Prevention |
|---|---|---|
| Cross-Site Scripting (XSS) | Inject malicious script into the page via unescaped input. | Escape output, avoid dangerouslySetInnerHTML, use CSP, sanitize (DOMPurify). |
| CSRF (Cross-Site Request Forgery) | Trick the browser into sending a request with cookies to another site. | SameSite cookie, CSRF token, Origin check |
| Clickjacking | Embed a transparent iframe site to trick clicks. | X-Frame-Options and frame-ancestors in CSP |
| Secret leak | API key/token embedded in client bundle | Never expose server-side secrets in client code; use a proxy instead. |