Frontend CoachResourcesSystem Design
Sign inGet started

Contents

Platform

  • Introduction & RADIO
  • Clarify the requirements.
  • Overall architecture

Core Topics

  • Rendering Patterns
  • Performance
  • Networking & API
  • State Management
  • Component Design

Advanced

  • Caching & Storage
  • Real-time Updates
  • Accessibility & i18n
  • Security

Deep foundation

  • Browser Rendering
  • Event Loop & Async
  • Network Protocols
  • JS Engine (V8)
  • HTTP Caching & CORS

Deep technical techniques

  • Design System
  • Testing Strategy
  • Monitoring
  • Build & Deploy
  • Bundler comparison
  • Advanced Testing
  • Storybook & Docs
  • Module Federation
  • Distributed Tracing

Expand

  • Asset Optimization
  • PWA & Offline
  • Scalability

Practice

  • Case Study: News Feed
  • Case Study: Chat
  • Case Study: Autocomplete
  • Case Study: Photo Gallery
  • Case Study: Infinite Table
  • Case Study: Video Player
  • Case Study: Google Docs
  • Interview Checklist
  • Question Bank
Cheatsheet

Interview Handbook

Designing a Frontend System from A to Z

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.

33Core Topic
54+Diagrams & Tables
70+Technical Interview Review Questions
7Full case study
01 — FOUNDATION

What is Frontend System Design & the RADIO thinking framework?

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.

The RADIO framework — the backbone of every answer.

RADIO is a popular mnemonic to help you not miss any steps. Proceed sequentially but be flexible to revisit steps when needed.

R Requirements Clarify the requirements. A Architecture Architecture & Components D Data Model Data model I Interface (API) API contract/props O Optimizations Optimization & Deep-Dive
The RADIO process
StepGoalSuggested duration
R — RequirementsUnderstand the problem: functionality, users, constraints, devices.~5 minutes
A — ArchitectureDraw the major components & data flow between them~12 minutes
D — Data ModelState definition: server data vs. client/UI state~8 minutes
I — Interface/APIThe contract between client and server, and between components (props).~8 minutes
O — OptimizationsPerformance, a11y, network, UX, security — deep dive.~12 minutes
Interview Tips

Think out loud and continuously confirm with the interviewer: "I'll assume X, does that sound okay to you?" They evaluate the communication process and trade-offs more than a single "correct" answer.

Key points to remember

FE System Design = Intentional Trade-offs. There is no perfect solution; every choice comes with a cost. Your job is to pick the right one for the right context and explain why.

Self-assessment quick check

🧩Quick Check

What is the core difference between frontend system design and backend system design?

02 — FOUNDATION

Requirements Clarification

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.

Classification of requirements

⚙️

Functional

What does the product do? Users can: create posts, like, comment, infinite scroll, upload images...

📐

Non-functional

How well does the product perform? Load speed, smoothness, offline capability, a11y, SEO.

📱

Constraints

Device (mobile/desktop), supported browsers, network (3G?), multilingual support, framework.

Essential questions to ask.

  • **Users & Scale:** Who uses it? How many concurrent users? Primary device types?
  • Core features: Which features are mandatory for MVP? Which are "nice-to-have"?
  • Data: Does the data change frequently? Is real-time required?
  • Performance: Are there specific targets (e.g., LCP < 2.5s)?
  • SEO: Does the page need to be indexed by Google? (Significantly impacts rendering pattern)
  • Offline: Does it need to work when there is no network connection?
  • Cross-platform: Web-only or need to share logic with mobile app?
  • i18n / a11y: Do you need to support multiple languages, RTL, and screen readers?
Common mistakes

Spending too little (or too much) time on this step. The goal is to finalize the scope within ~5 minutes to establish a clear "contract" for the remainder of the session.

Practical Tips

Write the finalized list of requirements in a corner of the board (or a separate cell). When diving deeper, you can always point to it to explain why a feature was chosen or omitted.

🧩Quick Check

Which of the following requirements has the MOST DIRECT and SIGNIFICANT impact on choosing a rendering pattern (CSR/SSR/SSG)?

03 — FOUNDATION

Overall architecture & component separation

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.

Common client architecture (layered)

Server / API / CDN REST · GraphQL · WebSocket CLIENT APPLICATION Data / Network Layer API client · caching · retry · auth tokens · normalize State / Store Layer Server cache · global UI state · derived/selector View Model / Controller Hooks, container components, event handlers, business logic View / UI Components Presentational, "dumb" components — only render from props
The layered architecture of a Single Page Application (SPA)

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.

Container / Presentational pattern

🧠

Container ("smart")

Knowledge of data & logic. Calling APIs, managing state, handling events. Not concerned with specific display.

🎨

Presentational ("dumb")

Only receives props and renders UI. No business state. Easy to test, easy to reuse, easy to build Storybook.

Why separate it like that?

Reusability (UI reused in multiple places), easy to test (logic separated from DOM), and easy to maintain (changing API does not affect UI and vice versa).

When to use Micro-frontends?

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.

Independent team deployment- Split a large codebaseDuplicate bundles- Integration Complexity- UX is most prone to inconsistency
🧩Quick Check

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.

04 — CORE

Rendering Patterns: CSR · SSR · SSG · ISR

Core question: Where and when is HTML generated? This choice affects SEO, rendering speed, server cost, and complexity.

Visually compare the timeline.

CSR Empty HTML Load JS bundle Fetch data Render UI ✓ The user sees the content late. SSR Server renders full HTML. The user sees the content ✓ Hydration (attaching JS) → interaction Content appears first, interaction comes later. SSG Pre-built HTML at deployment CDN responds immediately ✓ Fastest, but data is static at build time.
When is HTML ready — CSR vs SSR
PatternWhen is HTML created?SEOFCP speedGood for
Client-Side Rendering (CSR)In the browser, after JS runsWeak (requires workaround)First-time slowDashboard, post-login app, minimal SEO needed.
SSR (Server-Side Rendering)For each request, on the serverGood.Fast (TTFB depends on server)Personalized page, data changes continuously, requires SEO.
SSG (Static Site Generation)During build, before deploymentBest.Fastest (CDN)Blog, documentation, marketing, landing page
ISR (Incremental Static Regeneration)Pre-built + periodic regenerationGood.FastE-commerce, many pages but updates are not continuous.

Hydration & modern variants

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:

  • Streaming SSR: the server sends HTML in chunks, and the browser renders progressively instead of waiting for the entire page.
  • Progressive / Selective Hydration: hydrate critical parts first (e.g., the portion the user is currently viewing).
  • Islands Architecture: most of the page is static HTML, only a few interactive "islands" require JavaScript (Astro, Qwik).
  • RSC (React Server Components): components run on the server, no JavaScript is sent to the client for that part.
How to answer intelligently

Don't say "always use SSR." Instead, say: "Since the page needs SEO and content varies by user, I choose SSR; but for the post-login dashboard, CSR is sufficient because it doesn't need indexing." → This demonstrates context-driven thinking.

Flashcards: quick review of concepts

Flashcards3
🧩Quick Check

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?

05 — CORE

Performance & Core Web Vitals

Performance isn't just about being "fast"—it's about being fast at the moments users perceive. Google measures this through Core Web Vitals.

Core Web Vitals — 3 metrics to remember.

🎨

LCP

Largest Contentful Paint. The time it takes for the largest element to appear. Good: < 2.5s

👆

INP

Interaction to Next Paint. Interaction response delay (replaces FID). Good: < 200ms

📏

CLS (Cumulative Layout Shift)

Cumulative Layout Shift. Layout shift level. Good: < 0.1

Optimal levers — categorized by type.

Performance 4 optimization axes 🌐 Network Code splitting · lazy load Compression · HTTP/2 · CDN · preload 🖼️ Assets WebP/AVIF images · responsive Font subset · tree-shaking · minify ⚙️ Render / JS Virtualize list · memo Debounce · web worker · avoid reflow ✨ Perceived Skeleton · optimistic UI Prefetch · streaming · placeholder
Performance optimization map

Code splitting and lazy loading are techniques used to optimize the loading performance of web applications. Code splitting allows you to break your application into smaller chunks, which can be loaded on demand rather than all at once. This reduces the initial load time and improves the user experience. Lazy loading is a strategy that delays the loading of non-essential resources until they are needed. This means that components or modules are only loaded when they are required, further enhancing performance and reducing the amount of data that needs to be transferred during the initial load. Together, these techniques help in creating more efficient and responsive applications.

Instead of sending all JavaScript at once, only load the necessary parts. This is one of the most effective optimizations for FCP/LCP.

React lazy
// Split bundle by route — load only when needed
const Dashboard = React.lazy(() => import('./Dashboard'));

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

List Virtualization — a key technique for handling long lists.

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.

Real list: 10,000 items viewport Only render. Actual DOM: ~10–15 items Lightweight DOM Smooth scrolling. Low RAM
Windowing / virtualization mechanism

Popular libraries: react-window, react-virtuoso, @tanstack/virtual.

Don't optimize prematurely

Always measure first, optimize later. Use Lighthouse, Performance tab, and React Profiler to identify real bottlenecks instead of guessing. Optimizing the wrong place wastes time and complicates the code.

Flashcards Performance

Flashcards3
🧩Quick Check

A page displaying a list of 50,000 products lags heavily when scrolling. What is the most optimal solution?

06 — CORE

Networking & Client-side API Design

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 vs GraphQL REST (Representational State Transfer) and GraphQL are two different approaches to building APIs. REST is an architectural style that uses standard HTTP methods (GET, POST, PUT, DELETE) to interact with resources. Each resource is identified by a unique URL, and the server responds with the entire resource representation. This can lead to over-fetching or under-fetching of data, as clients may receive more or less information than needed. GraphQL, on the other hand, is a query language for APIs that allows clients to request exactly the data they need. It provides a single endpoint and enables clients to specify the structure of the response. This reduces over-fetching and under-fetching issues, as clients can tailor their requests to their specific requirements. In summary, REST is resource-oriented and can lead to inefficiencies in data retrieval, while GraphQL is query-oriented and offers more flexibility in data fetching.

RESTGraphQL
Fetch dataMultiple fixed endpoints1 endpoint, client declares the required fields themselves.
Over/Under-fetchingProne to (over-fetching/under-fetching)Minimize as much as possible.
CachingSimple (HTTP cache by URL)More complex (requires normalization)
VersioningCommonly /v1, /v2Schema evolution, deprecate field.
"Matches"Simple API, cache by URLComplex UI with multiple data relationships.

Pagination: 3 strategies

🔢

Offset / Page

?page=2&limit=20. Easy to understand, supports page navigation. Drawback: data inconsistency when new items are inserted.

🔖

Cursor-based

Stable with changing data, suitable for infinite scroll and feeds. Difficult to jump to arbitrary pages.

♾️

Infinite Scroll

UI loads more when scrolling to the end (IntersectionObserver), typically using a cursor below.

This is a commonly asked question.

"Which pagination should social media feeds use?" → Cursor-based. Because feeds continuously have new posts inserted at the top; offset would cause duplicate/missed items when scrolling.

Handling Race Condition in search/autocomplete.

When a user types "rea" quickly → "react", the request for "rea" may arrive AFTER "react" and overwrite the correct result. Solution:

AbortController
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.

Resilience: retry, timeout, backoff

  • Timeout: do not leave requests hanging indefinitely — cancel after N seconds.
  • Retry with exponential backoff: retry at 1s → 2s → 4s to avoid overwhelming the server.
  • Idempotency: For POST requests that create data, use an idempotency key to retry without creating duplicates.
  • Graceful degradation: serve stale cache / display user-friendly error messages instead of a white screen.
🧩Quick Check

In an autocomplete field, how do you ensure the displayed results always match the latest keyword the user has typed?

07 — CORE

State Management

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.

State classification — decision diagram

Where does this state come from? Server hay Client? from the server Server State DS articles, profiles, products... → React Query / SWR / RTK Query from the client/UI Used in multiple places? No. Local State input, toggle, hover... → useState / useReducer Yes. Global UI State theme, auth, modal, cart → Context / Zustand / Redux URL State filter, tab, search query → store in URL for sharing and correct back/forward behavior.
Decision tree: where to place state?

Four types of state to distinguish:

TypeDescriptionThe right tool
Server StateList of articles, user profiles (requires caching, sync, refetch)React Query, SWR, RTK Query, Apollo
Local StateInput value, open/close dropdownuseState, useReducer
Global UI StateTheme, login, cart, global modalContext, Zustand, Redux, Jotai
URL StateSelected tab, filters, search keywordsRouter (query parameters)
Common Misunderstandings

Many people stuff server data into Redux and write their own cache/loading/error logic. In reality, server state is very different from client state: it's asynchronous, can become "stale", and requires refetching & deduplication. Let React Query/SWR handle that.

One-way Flux / Redux

View Action Reducer Store Store updates → view re-renders
Unidirectional data flow

One-way data flow makes debugging easier: every state change goes through action → reducer, so we always know "why the state changed."

🧩Quick Check

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?

08 — CORE

Component Design & API Design

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.

Principles of Component API Design

  • Single Responsibility: each component does one thing well.
  • Composition over configuration: prioritize composing smaller components rather than a single massive component with 30 props.
  • Controlled vs Uncontrolled: allow both when appropriate (value + onChange, or defaultValue).
  • Sensible defaults: works immediately with minimal props.
  • Default accessibility: ARIA roles and focus management are built-in.

Example: design an API for the Autocomplete component.

Props API
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;
}
Advantages during the interview

When designing Autocomplete, proactively mention: debounce, race condition (AbortController), caching results, keyboard navigation (↑↓ Enter Esc), ARIA (role="combobox", aria-activedescendant), and virtualization for long lists. This is the checklist interviewers expect.

The Compound Components pattern is a design pattern in React that allows you to create components that work together as a single unit. This pattern enables better composition and flexibility by allowing child components to communicate with their parent component without needing to pass props explicitly. It promotes a more intuitive API and enhances the reusability of components.

Allows users to compose child components flexibly, sharing state implicitly via Context.

Compound API
<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>
Design thinking

**Question:** "What customizations will users of this component need?" Too few props → rigid. Too many props → hard to use. Compound components and render props/children help balance flexibility without exploding props.

🧩Quick Check

When designing an Autocomplete component for an interview, which of the following factors is OFTEN FORGOTTEN but highly valued?

09 — ADVANCED

Caching & Client Storage

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."

Client-side cache layers

App / Memory React Query cache Storage localStorage / IDB Service Worker Cache API (offline) HTTP cache CDN / browser Server The closer to the app (left), the faster but the more prone to "stale" data → requires an invalidation strategy.
The journey of a request and potential caching points.

Choose client-side storage.

The mechanismCapacityCharacteristicsUsed for
**Memory (JS)**RAMLost when reloadingCache runtime, state
localStorage5–10 MBSynchronous, string only, long-lived.Theme, small token, prefs
sessionStorage~5MBLost when closing the tabSession-based temporary data
IndexedDBLarge (hundreds of MB+)Asynchronous, store objectOffline data, big data
Cookies~4KBAttach to every requestSession ID (HttpOnly), auth
Cache APILargeThrough Service WorkerCache response, PWA offline
Token security

Don't store sensitive tokens in localStorage if you're concerned about XSS — it's accessible via JavaScript. Cookies with HttpOnly + Secure + SameSite flags are safer for sessions, since JavaScript cannot read them.

Cache invalidation strategy

TTL (time-to-live)stale-while-revalidateCache-then-networkInvalidate on mutationETag / Last-Modified

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.

🧩Quick Check

You need to store several hundred MB of data for the app to work offline. Which mechanism is suitable?

10 — ADVANCED

Real-time Updates

Chat, notifications, stock prices, "typing..." — when to use polling, SSE, or WebSocket? Each has its own trade-offs in latency, data direction, and complexity.

Short Polling Client Server Polling — simple, costly in requests, delayed. SSE (Server-Sent Events) Open once. Server continuously pushes one-way — feed/notification aggregation WebSocket Two-way, full-duplex channel — chat, gaming, collaboration
Compare real-time mechanisms
The mechanismAfternoonComplexityGood for
Short PollingThe client asked:LowNo rush to update, prototype.
Long PollingClient asks (keep connection)AverageFallback when WebSocket is unavailable
SSEServer → Client (one-way)Low–MediumNotification, feed, log stream
WebSockettwo-wayCaoChat, collaboration, gaming, trading
How to quickly select

Server push only? → SSE (lightweight, auto-reconnect, over HTTP). Need high-frequency bidirectional? → WebSocket. Not urgent/simple? → Polling. Always consider reconnection, heartbeat, and fallback.

Optimistic UI for a real-time feel.

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.

🧩Quick Check

You are building a 1-on-1 chat feature with "typing..." indicators and instant two-way messaging. What is the most suitable mechanism?

11 — ADVANCED

Accessibility (a11y) and Internationalization (i18n)

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.

Core Accessibility Checklist

⌨️

Keyboard

All operations can be performed using the keyboard. Focus is clearly visible, tab order is logical, and there is no focus trap.

🏷️

Semantic HTML

Use correct semantic tags (button, nav, main) before considering ARIA. Proper HTML equals free accessibility.

🔊

ARIA & screen reader

aria-label, role, aria-live for dynamic content. Only use ARIA when HTML is insufficient.

🌗

Contrast & Motion

Color contrast meets WCAG (4.5:1), respects prefers-reduced-motion.

The first rule of ARIA

"No ARIA is better than bad ARIA." Prioritize semantic HTML. A real `

12 — ADVANCED

Frontend Security

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.

Common threats

AttackThe essencePrevention
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
ClickjackingEmbed a transparent iframe site to trick clicks.X-Frame-Options and frame-ancestors in CSP
Secret leakAPI key/token embedded in client bundleNever expose server-side secrets in client code; use a proxy instead.

XSS — the number one enemy of Frontend.

Attacker Inject App render If you do NOT escape Victim The script runs and loses the cookie. 🛡️ Block here: escape/sanitize + CSP + HttpOnly cookie
How XSS works & blocking points
React helps you (partially)

React automatically escapes values in JSX, so `{userInput}` is safe against XSS. Danger only arises when you use `dangerouslySetInnerHTML` — in that case, you MUST sanitize with DOMPurify first.

The golden rule

Never trust data from the client. All frontend validation is only for good UX; real validation must happen on the server. The frontend can be opened via DevTools and anything can be modified.

🧩Quick Check

When must you **mandatorily** sanitize HTML (e.g., using DOMPurify) in React?

13 — Deep Foundation

How does the browser render a page?

Understanding the render pipeline is key to optimizing performance in the right places. Every technique—such as avoiding layout thrashing, using transform instead of top/left, or leveraging content-visibility—stems from this foundation.

Critical Rendering Path — 6 Steps

HTML → DOM tree CSS → CSSOM tree Render Tree DOM + CSSOM Layout calculate size/position Paint Draw pixels Composite GPU layer fusion Fix geometry (width, top) → re-run from Layout (most expensive). Fix transform/opacity → Only Composite (cheap, runs on GPU).
From bytes to pixels on the screen

Reflow vs Repaint — why is it important?

TypeWhen does it occur?Cost
Reflow (Layout)Resizing, repositioning, adding/removing DOM elements, reading offsetHeight...Expensive — must recalculate the geometry of the entire tree.
RepaintChange color, visibility, box-shadow (no layout change)Average — pixel repaint
Composite-onlyChange transform, opacityCheapest — GPU compositing layers, skipping Layout & Paint
Layout Thrashing

Reading layout properties (offsetTop, getBoundingClientRect) immediately after writing to the DOM forces the browser to perform synchronous reflows repeatedly. To avoid this: batch all reads together before writing (read-then-write batching), or use requestAnimationFrame.

A high-scoring answer.

"For smooth 60fps animation, I only animate transform and opacity because they run on the compositor thread, do not block the main thread, and do not cause reflow." → demonstrates your understanding of the pipeline.

🧩Quick Check

Which CSS property, when animated, will NOT cause reflow or repaint, only composite?

14 — DEEP FOUNDATION

Event Loop & Asynchronous in JS

JavaScript runs on a single thread but can handle thousands of tasks thanks to the event loop. Understanding the order of microtasks vs macrotasks helps you debug those "hard-to-understand" bugs related to execution order.

Mechanism: Call Stack, Queue & Loop

Call Stack main() Synchronous execution LIFO Event Loop Microtask Queue Promise.then, queueMicrotask HIGH PRIORITY Macrotask Queue setTimeout, event, I/O Run one per loop iteration Each round: run through the entire stack → CLEAN UP microtasks → get 1 macrotask → render → repeat.
How does the event loop coordinate?

Execution order — classic example

JS
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
// Synchronous (1,4) runs first → microtask (3) → macrotask (2)
**The Unforgettable Rule**

After each macrotask, the event loop clears the ENTIRE microtask queue before rendering and fetching the next macrotask. Therefore, Promise.then always runs before setTimeout, even if setTimeout is set to 0ms.

Trap: infinite microtask

If a microtask continuously creates new microtasks, rendering will suffer from starvation — the page freezes because the event loop never reaches the paint step. For repeated heavy tasks, use setTimeout/requestIdleCallback to yield the thread.

🧩Quick Check

The output of: setTimeout(()=>log('A'),0); Promise.resolve().then(()=>log('B')); log('C') is: C, B, A

15 — DEEP FOUNDATION

Network protocol: HTTP/1.1 → HTTP/3

Each HTTP version addresses a specific bottleneck. Understanding them helps you explain why bundling was once important, and why with HTTP/2, "file concatenation" is no longer necessary.

Compare the versions.

VersionKey featuresThe remaining issue
HTTP/1.11 request/response per connection at a time; must open multiple parallel connections.Head-of-line blocking; limit of ~6 connections per domain → requires bundling, sprites.
HTTP/2Multiplexing: multiple streams over a single connection; header compression; server push.Still HOL blocking at the TCP layer (losing one packet blocks the entire connection).
HTTP/3Runs on QUIC (UDP); each stream is independent; faster handshake.New, some older networks block UDP.

TLS handshake and why HTTPS is "slow the first time."

DNS IP TCP 3-way handshake TLS key exchange Request send & wait TTFB first byte Each step costs one round-trip. Use connection reuse, preconnect, HTTP/3 to cut down
The lifecycle of an HTTPS request
Practical Optimization

Use `` for critical third-party domains (CDN, API), and `dns-prefetch` for less important domains. This completes DNS/TCP/TLS early, shaving off a few hundred milliseconds when the actual request occurs.

Common interview questions

"With HTTP/2, should you still bundle all JS into one file?" → No. Multiplexing eliminates the penalty for multiple small files, and actually benefits caching (fixing one module won't invalidate the cache for the entire bundle).

🧩Quick Check

HTTP/2 reduces the need to bundle multiple JS files into one large bundle because it supports multiplexing, allowing multiple requests to be sent concurrently over a single TCP connection. This eliminates the overhead of multiple connections and reduces latency, making it efficient to load many small files individually rather than combining them into a single large file.

16 — DEEP FOUNDATION

JavaScript Engine: How V8 Works Internally

Understanding the engine helps you write "engine-friendly" code — faster without blind micro-optimization. V8 (Chrome, Node) is a prime example with its intelligent JIT compilation pipeline.

V8's compilation pipeline

Parser source → AST Ignition interpreter → bytecode Profiler Detect "hot" code (run multiple times) TurboFan JIT → machine code highly optimized Deoptimization: if an assumption is wrong (type change) → revert to bytecode
From source code to machine execution

Hidden Classes & Inline Caching

V8 does not know your object's "shape" in advance. It creates a hidden class (shape) for each object structure. Objects with the same shape share a hidden class → property access is extremely fast. Changing the shape (adding/deleting properties, adding in a different order) breaks this optimization.

JS
// GOOD: same shape, V8 can optimize
function Point(x, y) { this.x = x; this.y = y; }
const a = new Point(1, 2);
const b = new Point(3, 4); // The same hidden class as A.

// BAD: Adding a property later, different order → different shape
a.z = 5;            // Now you have a different shape.
delete a.x;         // Deleting properties breaks hidden classes → slow, avoid using it.

Garbage Collection — Generational

RegionWhat does it contain?GC (Garbage Collection)
Young generationMost new objects "die young".Scavenge — fast, frequent
Old generationObject survives multiple GC cyclesMark-Sweep-Compact — slower, less frequent
Common memory leaks in frontend development

Listener not removed, setInterval not cleared, closure holding large references, or detached DOM nodes still held by JavaScript. In React: forgetting cleanup in useEffect. Use DevTools → Memory to take heap snapshots and find leaks.

Don't blindly micro-optimize.

The engine is extremely intelligent; writing clear code with consistent shape and avoiding deletions in hot paths is sufficient. Measure with a profiler before optimizing — "premature optimization is the root of all evil."

🧩Quick Check

Why should all properties of an object be initialized in the same order, within the constructor?

17 — DEEP FOUNDATION

Detailed HTTP Caching & CORS

Proper caching is a "free" way to boost performance and reduce server load. CORS is something every frontend developer has struggled with — understanding it helps you debug quickly instead of "trial and error."

Important caching headers

HeaderEffect
Cache-Control: max-age=31536000, immutableCache for 1 year, no revalidation needed — for assets with content hash.
Cache-Control: no-cacheMust revalidate with the server before using (NOT "no cache")
Cache-Control: no-storeAbsolutely do not store — for sensitive data.
ETag + If-None-MatchContent fingerprinting; server returns 304 Not Modified if unchanged → saves bandwidth.
stale-while-revalidateUse the old version immediately, update silently — fast + gradually new
Classic caching strategy

Files with content hashes (e.g., app.a3f9.js) are cached immutably for 1 year because changing content changes the filename. The index.html file uses no-cache (always revalidate) so users receive the latest version. This is the foundation of safe deployment.

CORS — why is the request blocked?

Browser Server (API) 1. OPTIONS (preflight): "I want to send a PUT request, is header X allowed?" 2. Access-Control-Allow-Origin/Methods/Headers 3. Actual Request (PUT) — if preflight allows
Preflight request (OPTIONS) for "non-simple" requests
CORS is a browser mechanism.

CORS does not protect the server (requests still reach the server). It protects the user: the browser blocks JavaScript from reading responses from a different origin unless the server allows it. Therefore, CORS errors must be fixed on the server (by adding headers), not on the client.

Trick question

"Does no-cache mean no caching?" → No! no-cache = still stores but must revalidate (ETag) before using. "Don't store anything" is no-store. Confusing these two is a very common mistake.

🧩Quick Check

A CORS error appears in the console. Where is the correct place to fix it?

18 — ADVANCED TECHNIQUES

Design System & Component Library

As the product and team grow, UI consistency becomes a technical challenge. A design system serves as the "single source of truth," enabling dozens of developers to create consistent interfaces without stepping on each other's toes.

The layers of a Design System.

Design Tokens Color, spacing, font, radius — primitive variables. Primitives / Base components Button, Input, Text — use tokens, no business logic Patterns / Composite Form, Modal, DataTable — composed from primitives The real product
From token to product

Design Tokens — the heart of theming.

CSS
/* Token semantic points to the original token → changing the theme is easy. */
:root {
  --blue-500: #3b82f6;        /* primitive */
  --color-primary: var(--blue-500); /* semantic */
}
[data-theme="dark"] {
  --color-primary: var(--blue-300);
}
/* The component only uses semantic tokens, no hardcoded colors. */
.btn { background: var(--color-primary); }
Versioning & Breaking Changes

Component libraries should use semantic versioning. Changing a required prop constitutes a major version. Provide codemods for automatic migration, and issue deprecation warnings at least one version before removal. This maintains trust with the teams using the library.

Notable trade-off

"Flexible" components (many props) vs. "opinionated" components (fewer choices, high consistency). Good libraries often use compound components and slots to achieve both flexibility and consistency, rather than cramming in 30 boolean props.

🧩Quick Check

Why should "semantic tokens" (--color-primary) be separated from "primitive tokens" (--blue-500)?

19 — ADVANCED TECHNIQUES

Testing Strategy for Frontend

Tests are not about achieving 100% coverage, but about having confidence to refactor and deploy. The core question: what to test, at which layer, to catch the most bugs with the lowest maintenance cost.

Testing Pyramid (and the variant "Trophy")

E2E Integration Unit Few · Slow · Expensive The entire user flow. Sweet spot Multiple components working together Many · fast · cheap pure function/logic
Distribute tests by layer

What type of tests catch which bugs?

TypeWhat to testPopular tools
UnitPure function, util, reducer, custom hookJest, Vitest
Component / IntegrationComponent renders correctly, user interaction, callback invocationReact Testing Library
E2EComplete flow: login → purchase → paymentPlaywright, Cypress
Visual regressionDoes the UI break visually?Chromatic, Percy
The philosophy of Testing Library

"Test like how users interact" — find elements by role/label/text instead of class or internal test-id. Such tests are less brittle during refactoring and catch real bugs. Avoid testing implementation details (internal state, function names).

Trap: flaky tests

E2E tests become "flaky" due to timing issues (waiting for animations, network). Mitigation: wait for conditions (await expect(...).toBeVisible()) instead of sleep(2000), mock network when needed, and isolate tests (no dependency on execution order).

🧩Quick Check

According to the philosophy of React Testing Library, which element-finding method is most recommended?

20 — ADVANCED TECHNIQUES

Monitoring and Observability

"You cannot improve what you don't measure." After deployment, how do you know if real users encounter errors or if the page is slow? This is what distinguishes junior from senior engineers.

Three pillars to monitor.

🐞

Error Tracking

Catch JS runtime errors, promise rejections, and API errors. Group them by category, include stack trace and breadcrumbs. Tools: Sentry, Rollbar.

📊

RUM — Real User Monitoring

Measure Core Web Vitals (LCP, INP, CLS) from real users, segmented by device/network/region. This differs from lab tests (Lighthouse).

📈

Analytics & Funnel

At which step did the user drop off, and which feature was used? Connect performance to user behavior and revenue.

Performance Budget — set limits to avoid "drifting."

JSON
// Warn/block the build if it exceeds the threshold.
{
  "budgets": [
    { "resourceType": "script", "maximumError": "170kb" },
    { "metric": "LCP", "maximumWarning": "2500ms" }
  ]
}
Lab data vs Field data

Lab (Lighthouse) = simulated environment, stable, good for debugging. Field/RUM = real users, many device/network variations — these are the numbers that matter for SEO (Google uses CrUX). Must look at both.

Source maps for production

Upload private source maps (not public) to Sentry so that stack traces from minified code become readable. Do not deploy source maps publicly to avoid source code exposure.

🧩Quick Check

Google uses field data from the Chrome User Experience Report (CrUX) to evaluate Core Web Vitals for search rankings.

21 — ADVANCED TECHNIQUES

Build, Bundling & Deploy

The code you write doesn't run directly in the browser. Understanding bundlers helps you reduce bundle size, improve load speed, and deploy safely with feature flags and rollback.

What does a bundler do?

TechniqueEffect
Tree-shakingRemove dead code — requires ES modules for static analysis.
Code splittingSplit bundles by route/component, load on demand (dynamic import()).
MinificationMinify variable names, remove whitespace, dead code
Content hashingFile name with hash (app.a3f9.js) → permanent cache, content change = name change
Module FederationShare the runtime module between apps (micro-frontend platform).

CI/CD Pipeline & Deployment Strategy

Commit push PR CI lint·test·build Preview Temporary deployment Canary 5% of users Production 100% + built-in rollback Canary/blue-green Helps detect errors early on a small number of users, easy to rollback if errors occur.
From commit to production safely
Feature flags = decoupling deployment from release

Deploy code to production but disable the feature behind a flag. Gradually enable it from 1% → 50% → 100%. If an error occurs, turn off the flag immediately (no need to rollback the deployment). Also used for A/B testing.

A valuable question

How to deploy without breaking the experience for users on an old tab? → Use versioned assets with content hashes, keep old files for a period of time, and handle "chunk load failed" errors by reloading the page when a new version is detected.

🧩Quick Check

The main benefit of feature flags compared to using deploy/rollback is that they allow you to toggle features on or off at runtime without redeploying, enabling safer, more granular control over releases, faster rollback, and easier A/B testing or gradual rollouts.

22 — ADVANCED TECHNIQUES

Bundler comparison: Webpack · Vite · esbuild · Rollup

Each build tool is created to solve a different problem. Understanding them helps you choose the right tool and explains why Vite is "lightning fast" during development.

Overview comparison

ToolStrong pointsTechnical specifications
WebpackComplex app, massive loader/plugin ecosystemBundle everything; extensive configuration; module federation
ViteExtremely fast dev experience.Dev: Native ESM + esbuild, no bundling; Prod: Rollup
esbuildRaw build speed (written in Go)10–100× faster than JS tools; fewer advanced features
RollupBuild a libraryExcellent tree-shaking, clean output (ESM/CJS/UMD).

Why is Vite fast during development?

Webpack (dev) module A module B module C… Bundle COMPLETELYThen run it. Large project = long wait every time you start it. Vite (dev) BrowserModule requirements Vite servesESM on demand Instant startup, only build the module being viewed.
Webpack bundles everything upfront, while Vite serves ESM on demand.
The core idea of Vite

Modern browsers support native ES modules. During development, Vite does not bundle but lets the browser import each module individually; Vite only transforms the modules that are requested (using esbuild for extremely fast processing). As a result, server startup and HMR are nearly instantaneous, regardless of project size. For production builds, Vite bundles using Rollup for optimization.

How to choose a tool

For building libraries → Rollup (clean output, good tree-shaking). For building new apps → Vite (excellent DX). For legacy/complex projects requiring specific plugins → Webpack. For raw speed in the pipeline → esbuild.

🧩Quick Check

Why does Vite start the dev server almost instantly even with large projects?

23 — ADVANCED TECHNIQUES

Advanced Testing: Mock, Async & Contract

After mastering the testing pyramid, here are the techniques that distinguish developers who write "runnable" tests from those who write "reliable" tests. Testing incorrectly is worse than not testing at all, as it creates a false sense of security.

Mock vs Stub vs Spy — don't get confused.

ConceptWhat is it?Used when
StubReplace the function with a fixed return value.Need a dependency that returns predefined data (mock API returns an object).
SpyTrack how the ACTUAL function is called (number of times, parameters).To confirm whether the callback/analytics is called correctly.
MockA mock object has expectations about how it is called.Check interaction between modules, fail if called incorrectly.
FakeSimple in-memory database implementation.Replace the heavy system with a lightweight version.
Don't over-mock.

Mocking everything makes tests only verify "the code calls the mocked function correctly" — it doesn't reflect real behavior. Prioritize mocking at system boundaries (network, time, storage), and keep the logic within the test running as real code.

Testing asynchronous code & timers

JS
// Use fake timers to test debounce WITHOUT waiting in real time.
vi.useFakeTimers();
const spy = vi.fn();
const debounced = debounce(spy, 300);
debounced(); debounced(); // Call 2 times consecutively.
vi.advanceTimersByTime(300); // "tua" 300ms
expect(spy).toHaveBeenCalledTimes(1); // runs only once
Mock the network with MSW

Mock Service Worker (MSW) intercepts requests at the network layer, so components use real fetch without needing code changes. The same mock works for both testing and development — preventing tests from "knowing too much" about how the API is called.

Contract Testing — preventing "API changes without anyone knowing"

When frontend and backend develop independently, contract tests ensure both sides still "understand each other": the frontend declares what response format it expects, and the backend confirms it provides that exact format. If the backend changes a field, the contract test fails immediately — catching the error before production.

A high-scoring answer

I test based on user behavior, mock at the network boundary using MSW, use fake timers for debounce/throttle, and apply contract tests for the FE-BE boundary. → This shows you understand testing at the correct layers.

🧩Quick Check

The main difference between "spy" and "stub" is that a spy records information about how a function is called (e.g., number of calls, arguments), while a stub replaces a function with a predefined behavior or return value, typically to isolate the code under test.

24 — ADVANCED TECHNIQUES

Storybook & Component Documentation

Isolating components from the app enables efficient development, testing, and documentation. Storybook is the standard tool for building a living library for a design system.

Why develop isolated components?

🔬

Isolated development

Build each component state (loading, error, empty, full) without running the entire app or creating complex data.

📖

Living document

Each "story" is a live example. Designers and developers can view all variants, props, and usage patterns.

👁️

Visual regression

Take screenshots of each story, compare when changes occur → catch UI "breakage" that unit tests cannot detect.

♿

A11y audit

Addon to check accessibility directly in the story: contrast, ARIA, focus order.

Structure of a "story"

JS
// Button.stories.tsx — each export is one state
export default { title: 'UI/Button', component: Button };

export const Primary = { args: { variant: 'primary', children: 'Save' } };
export const Loading = { args: { loading: true } };
export const Disabled = { args: { disabled: true } };
Story = test + docs + dev simultaneously

A good story serves as documentation (for others to view), a playground (for developers to experiment), and input for visual regression tests. "Write once, use for three purposes" — this is why Storybook is worth investing in for teams with a design system.

Systems thinking

Mentioning Storybook when asked about design systems shows you're thinking about maintainability and collaboration, not just making code work. That's the mindset of someone building platform/infra for a team.

🧩Quick Check

What is the "3-in-1" benefit of a story in Storybook?

25 — ADVANCED TECHNIQUES

Module Federation — executing micro-frontends

This is the most common technical mechanism for implementing micro-frontends: allowing multiple apps to build and deploy independently while sharing code and rendering each other at runtime.

Host & Remote

Host (Shell App) General layout, router, remote loading. Remote: Cart Team A deploys separately. Remote: Search Team B deploys separately. Remote: Profile Team C deploys separately.
The host loads the module from a remote source at runtime.

Issues to address

ChallengeHow to handle
Duplicate dependencyDeclare shared resources (React, common libraries) to load once, avoiding multiple versions being downloaded.
Version mismatchSet singleton: true for React → prevents two React instances from conflicting.
Remote errors/slow performanceError boundary + fallback UI; don't let one remote crash the entire app.
UI consistencyShare the design system via a shared package.
The cost to pay

Module Federation adds significant complexity: managing shared dependencies, runtime versioning, difficulty debugging when remotes fail, and often worse performance than a monolith due to loading multiple runtimes. Only use it when the organizational benefits (independent teams) outweigh this cost.

Alternative

Not every micro-frontend requires Module Federation runtime. For lighter requirements: build-time integration (each team exports a package), iframe (strong isolation but limited UX), or web components. Choose based on the actual level of independence needed.

🧩Quick Check

Why is it necessary to declare React as a shared singleton in Module Federation?

26 — ADVANCED TECHNIQUES

Distributed Tracing & Alert Strategy

When a request flows through FE → API gateway → multiple services, how do you identify which step is slow? Tracing connects the pieces into a single picture. And alert properly to avoid being "ignored" due to too many alerts.

Trace, Span & Context Propagation

trace-id: abc123 FE — page load (1200ms) span: API gateway /feed (820ms) span: auth-service (110ms) span: db query (560ms) Looking at the waterfall, I can tell immediately. db query 560ms It’s a bottleneck — don’t guess blindly.
A trace consists of multiple spans that are sequential/nested.
The role of Frontend

The frontend initializes a trace-id and sends it as a header (e.g., traceparent per W3C Trace Context standard) with every API call. This links user-facing errors/latency to backend logs — making it feasible to debug "why user X experienced slowness."

Three Pillars of Observability

📝

Logs

Discrete events have timestamps. Good for investigating a specific case in detail.

📊

Metrics

Time-series aggregated metrics (p50/p95/p99 latency, error rate). Suitable for dashboards and alerts.

🔗

Traces

The journey of a request through services. Useful for identifying bottlenecks in a distributed system.

Properly using alerts — combating "alert fatigue"

The principleMeaning
Alert on symptoms, not on root causes.Report "error rate > 2%" (affects users) instead of "high CPU" (may be harmless).
Use percentile, not average.p95/p99 reflects the worst-case experience; averages hide the long tail.
ActionableEach alert must have a clear action; if nothing can be done, do not alert.
Reasonable thresholdToo sensitive → false alarms (cry wolf); too loose → missed real incidents.
Why p95 is more important than the average

"Average 200ms" sounds fine, but if p99 is 5 seconds, then 1% of users are suffering — often the most important ones (heavy data users). Seniors always look at tail latency, not just the average.

🧩Quick Check

Why should alerts be based on p95/p99 latency instead of average latency?

27 — EXTENSION

Optimize Images & Resources

Images often account for 50%+ of page weight. Proper optimization is the fastest way to improve LCP. This is a "low-hanging fruit" that many overlook.

Image formats — choose the right one.

FormatUsed whenNotes
AVIFScreenshots, background — top priority.Best compression, increasingly broad support.
WebPFallback for AVIFSmaller than JPEG/PNG by ~30%
SVGIcon, logo, vector imageInfinite resolution, small, can be styled with CSS.
JPEG/PNGThe final fallbackPNG for images requiring transparency.

Responsive images and lazy loading are techniques used to optimize web performance and improve user experience. Responsive images allow different image sizes to be served based on the user's device and screen resolution, ensuring that images are displayed at the appropriate size without unnecessary loading of large files on smaller screens. Lazy loading defers the loading of images until they are needed, typically when they come into the viewport. This reduces initial page load time and saves bandwidth, as images that are not immediately visible are not loaded until the user scrolls down to them. Implementing these techniques can significantly enhance the performance of a website, especially on mobile devices.

HTML
<!-- The browser automatically selects the best source + appropriate size. -->
<picture>
  <source type="image/avif" srcset="hero.avif">
  <source type="image/webp" srcset="hero.webp">
  <img src="hero.jpg" width="1200" height="630"
       loading="lazy" decoding="async" alt="...">
</picture>
Always set width & height

Declare width/height (or aspect-ratio) so the browser reserves space in advance — preventing layout shifts when images finish loading (improving CLS). Images without dimensions are the most common cause of CLS.

LCP images should NOT be lazy-loaded.

Images "above the fold" (typically LCP images) should use `loading="eager"` + `fetchpriority="high"`, and even preload. Lazy-loading them will worsen LCP. Only lazy-load images below the fold.

🧩Quick Check

Which images should have `loading="lazy"` applied?

28 — EXTENSION

PWA, Service Worker & Offline-first

A Service Worker is a background proxy between the app and the network, enabling the app to work offline, load instantly on subsequent visits, and receive push notifications. It is the foundation of an "app-like" experience.

Where does the Service Worker stand?

App (page) fetch() Service Worker Block the request. Decide between cache/network Cache Storage Saved resources Network When new data is needed
SW is a proxy between the app and the network.

Cache strategy

StrategyHow it works"Matches"
Cache FirstFetch from cache first, only go to network if not available.Static resources (CSS, JS, fonts, logos)
Network FirstGo to network first, use cache on error.Data needs to be fresh (feed, price)
Stale-While-RevalidateReturn cache immediately, while updating in the background.Avatar, content changes infrequently — fast + gradual updates.
Trap: SW "stuck" on an old version

Service Workers have their own lifecycle (install → waiting → activate). If updates are not handled properly, users may get stuck on an old version. A strategy of skip-waiting combined with a "New version available, reload?" notification is needed for smooth updates.

Requirements of PWA

To be installable as an app, you need HTTPS, a Web App Manifest (name, icon, theme), and a Service Worker. When these are in place, the browser shows an "Add to Home Screen" prompt.

🧩Quick Check

The most suitable caching strategy for feed data that must always be fresh but still needs a fallback when offline is **stale-while-revalidate**. This approach serves cached data immediately (stale) while fetching fresh data in the background, ensuring the user sees content even without a network connection. For critical freshness, combine it with a short max-age and a network-first strategy, falling back to cache only when the network fails.

29 — EXTENSION

Frontend in a large team scale.

When a codebase has hundreds of developers, the issue is no longer individual technical skill but how to organize so that many people can work in parallel without conflicts or slowing each other down.

Monorepo vs Polyrepo

CriteriaMonorepoPolyrepo
Share the code.Easy — shared repo, simultaneous updates.Difficult — via npm package, versioning
Synchronize changesA PR that fixes both the app and the library.Multiple PRs, multiple release steps
Build/CINeed smart tools (Nx, Turborepo) to only build the changed parts.Simple, independent per repository.
Team boundariesNeed clear CODEOWNERSNaturally split by repo.

Micro-frontends — when to use them.

Don't use it just because it "sounds cool."

Micro-frontends solve organizational problems (multiple teams deploying independently), not technical problems. The cost: complexity in shared dependencies, UI consistency, and performance (loading multiple runtimes). Only worthwhile when teams are large enough to block each other.

Senior mindset

"I'll start with a modular monolith with clear module boundaries. Only split into micro-frontends when there's evidence that teams are blocking each other during deployment." → shows you prioritize simpler solutions first.

🧩Quick Check

The most reasonable reason to adopt a micro-frontend architecture is to enable independent development, deployment, and scaling of frontend features by different teams, reducing coordination overhead and improving maintainability for large-scale applications.

30 — PRACTICE

Case Study: Chat Application (like Messenger)

A classic interview question. A chat app combines nearly every topic: real-time, optimistic UI, virtualized list, offline, and state synchronization. We follow the RADIO framework.

R — Requirements (clarify requirements)

A — Architecture

UI (Chat View) Virtualized message list + composer State Store Normalize messages by ID. WebSocket Layer Real-time send/receive + reconnect Local Queue offline-safe message queue IndexedDB Offline history
Client architecture of chat

D — Data Model

JS
// Normalized state: O(1) lookup, avoids duplication.
{
  messages: {
    byId: { "m1": { id, text, senderId, status, sentAt } },
    byConversation: { "c1": ["m1", "m2"] }
  },
  conversations: { byId: { "c1": { id, participants, lastMessageId } } },
  // status: 'sending' | 'sent' | 'delivered' | 'read' | 'failed'
}

I — Interface / Optimizations

⚡

Optimistic send

Display the message immediately with a "sending" status and a temporary ID. When the server acknowledges, replace it with the real ID. On error, mark it as failed and allow retry.

📜

Virtual list

Only render messages within the viewport. Load more on reverse scroll (infinite scroll upward), maintain scroll position.

🔌

Reconnect & Sync

WebSocket drops → exponential backoff. On reconnection, sync missed messages using the last timestamp/cursor.

⌨️

Typing & presence

Debounce typing events; send "typing" status at most every few seconds. Presence via periodic heartbeat.

Ensure no data loss

Each message has a client-generated ID (idempotency key). When resending due to network loss, the same ID is used, and the server handles deduplication. Combine a local queue with IndexedDB so that messages composed offline are sent when connectivity is restored.

O — Evaluation Criteria

The interviewer wants to hear your considerations: WebSocket vs SSE (chat requires two-way communication → WebSocket), how to handle incoming messages while scrolling through history, and resolving message order conflicts (sort by server timestamp).

🧩Quick Check

Why should you use a client-generated id (idempotency key) for each message?

31 — PRACTICE

Case Study: Autocomplete / Typeahead

A small but extremely common problem because it tests: debounce, race condition, caching, and keyboard accessibility — all in one component.

Key points to address correctly.

Type the key onChange Debounce ~300ms Cache? If available, reuse it. Fetch + AbortController Render dropdown a11y Cancel the old request when a new key is pressed → avoid race condition (old result overwrites new)
Flow from key press to result

Handling race conditions

JS
useEffect(() => {
  const controller = new AbortController();
  fetch(`/search?q=` + query, { signal: controller.signal })
    .then(r => r.json())
    .then(setResults)
    .catch(e => { if (e.name !== 'AbortError') handle(e); });
  // cleanup: cancel the old request when the query changes or on unmount
  return () => controller.abort();
}, [query]);
Accessibility — don't forget.

Use the combobox pattern: `aria-expanded`, `aria-activedescendant` pointing to the currently selected item, navigation with ↑↓, Enter to select, Esc to close. This is a point many candidates overlook, but interviewers pay close attention to.

Query caching

Cache results by query (Map). When the user deletes and retypes the same word, return immediately from cache — no API call. Combine with debounce to significantly reduce the number of requests.

🧩Quick Check

In autocomplete, a race condition occurs when the response from an earlier request arrives after the response from a later request, causing the UI to display outdated results. The correct way to handle this is to use a cancellation mechanism, such as an AbortController in fetch, or by tracking the latest request with a counter or timestamp to ignore stale responses.

32 — PRACTICE

Case Study: Photo Gallery (like Google Photos)

The problem of smoothly displaying thousands of images. Check: virtualization for the grid, lazy loading, responsive images, and lightbox. The core principle is "only work with what the user sees."

R — Requirements

A — Key Techniques

Viewport (actual rendering) Images outside the viewport: Do NOT render (keep only) height for correct scrolling Lightweight DOM There are ten thousand images.
Virtualized grid: only render images within the viewport.
🖼️

Responsive thumbnail

The server generates multiple sizes; srcset ensures each device loads the correct resolution. The lightbox only loads the full version.

⏳

Lazy + placeholder

Display average color or blur-up (LQIP) while loading → creates a fast feel and prevents layout shifts.

♻️

Reuse DOM node

When scrolling, reuse virtual nodes that have left the screen instead of creating new ones → reduces GC pressure.

🔍

Lightbox preload

When opening an image, preload the previous/next image to enable smooth transitions without waiting.

O — Points the interviewer pays attention to

How to calculate grid layout when images have different aspect ratios (justified layout like Google Photos requires knowing dimensions in advance — server returns width/height), and how to maintain scroll position when loading more images above.

🧩Quick Check

Why is virtualization important for a gallery of 10,000 images?

33 — PRACTICE

Case Study: Infinite Data Table

A massive data table (like Airtable, financial spreadsheets) with millions of rows and many columns. Check: bidirectional virtualization, server-side sort/filter, sticky header/column, and inline editing.

R — Requirements

A — Bidirectional Virtualization

Sticky header (always visible when scrolling vertically) Sticky col The actual render area. ~ rows × columns currently visible Millions of cells, but only a few dozen are created in the DOM at any given time.
Only render cells within the visible area (both rows & columns).

D — Server-side for sort/filter

HTTP
# Do not load all 1 million rows. Use server-side sort/filter/paginate; the client only requests a "window" of data.
GET /rows?sort=price:desc&filter=active&offset=4000&limit=100

# Or cursor-based for continuously changing data.
GET /rows?after=cursor_abc&limit=100
Why use sort/filter on the server side?

Sorting 1 million rows on the client will block the main thread. The server (with database indexing) handles this quickly and only returns the page of data the client needs. The client should only sort/filter locally when the dataset is small and fully loaded.

**Trap: dynamic height**

If each row has a different height (text wrap), virtualization becomes more difficult — you need to measure and cache actual heights, or use estimation with adjustment techniques. Fixed row height is much simpler.

🧩Quick Check

With a table of 1 million rows, where should sorting/filtering be done?

34 — PRACTICE

Case Study: Video Player (like YouTube)

Smooth video playback at all network speeds. Tests understanding of adaptive bitrate streaming (HLS/DASH), buffering, and building custom controls with accessibility.

R — Requirements

A — Adaptive Bitrate Streaming (ABR)

In the same video, multiple levels. 1080p · chunk 2-10s 720p · chunk 480p · chunk ABR Player bandwidth measurement + buffer Select level Fast network → high, slow network → low The manifest (.m3u8 / .mpd) lists the available levels and chunks. The player loads the next chunk at the appropriate level. in real time. Network drops mid-stream → subsequent chunks load at a lower quality, no need to pause the video.
The video is divided into chunks of multiple qualities, and the player selects based on network conditions.

Buffer & States

ConceptMeaning
HLS / DASHTwo streaming standards divide video into chunks + manifest. HLS is popular on Apple, DASH is an open standard.
Buffer aheadPreload several chunks to prevent lag when the network fluctuates.
Startup timeStart at a low level to play fast, then gradually increase as the network is measured.
MSE (Media Source Extensions)The browser API that allows JavaScript to "load" chunks into a video element — the foundation of an ABR player.
The core trade-off of ABR

High quality vs. rebuffering risk. ABR algorithm balances: prioritizes no interruptions (rebuffering severely harms user experience) over always delivering maximum quality. Starting low for fast playback is the standard strategy.

A11y for player

Controls must be keyboard accessible (Space for play/pause, ←→ for seek), include ARIA labels, support captions, and must not autoplay with sound (to avoid annoyance and a11y violations).

🧩Quick Check

When the user's network drops mid-stream, Adaptive Bitrate Streaming (ABR) works by detecting the decrease in available bandwidth through client-side monitoring. The player then automatically switches to a lower-bitrate video segment, reducing quality to prevent buffering. This process is seamless, as the video is divided into small chunks encoded at multiple bitrates, and the player requests the next chunk at the appropriate level based on real-time network conditions.

35 — PRACTICE

Case Study: Collaborative Editing (like Google Docs)

The hardest and most "valuable" problem: multiple users editing the same document in real-time without overwriting each other. Tests understanding of OT vs CRDT, presence, and conflict resolution.

R — Requirements

Core issue: concurrent editing

User A Insert "X" at position 5. User B Delete the character at position 3. OT / CRDT transform/merge operations Convergence Everyone = same result
Two people editing simultaneously — how to avoid overwriting?

OT vs CRDT — two approaches.

CriteriaOT (Operational Transformation)CRDT
The ideaTransform operations concurrently to maintain consistency.Self-merging data structure, no transformation needed.
Do we need a central server?Often requires (coordinating the order)Optional — suitable for P2P/offline
ComplexityComplex logic transformations are difficult to get right.Simpler in theory, but requires more metadata.
Used byGoogle Docs (traditional)Figma, many new apps, Yjs/Automerge
👥

Presence

Other users' cursor & selection sent via private channel (no persistence needed), throttle to reduce traffic.

📴

Offline editing

Save local operations when offline; when back online, merge using CRDT/OT — no data loss.

🕐

Version history

Save snapshot + operation sequence to rewind history and restore.

⚡

Local-first

Apply changes locally immediately (optimistic), sync in the background → no typing lag.

Why not use "last write wins"?

The simplest approach — last writer wins — will lose the other person's text. OT/CRDT ensures both concurrent operations are preserved and all clients converge to the same state. This is the difference between a "toy" and a real product.

O — How to make an impression

State that this is a "convergence in distributed systems" problem, compare OT vs CRDT with clear trade-offs, and mention local-first + offline. No need for transform algorithm code — understanding the principles and trade-offs is sufficient for the FE round.

🧩Quick Check

Why does collaborative editing NOT use the "last write wins" strategy?

36 — PRACTICE

Case Study: Designing a News Feed

Applying RADIO to a classic problem: designing a Facebook/Twitter-style feed. This is a template you can reuse for most feed-based questions.

R — Requirements

•

Functional

View feed (infinite scroll), create posts, like/comment, display images/videos, update when new posts appear.

•

Non-functional

Fast loading on mobile + 3G, smooth scrolling, supports a11y, and can handle intermittent network connectivity.

A — Architecture

App Shell + Router FeedContainer (smart) fetch + cache + pagination state PostComposer Create a post + optimistic VirtualizedList windowing + IntersectionObserver NewPostsPill "New X articles" (SSE/poll) PostCard (dumb) Header · Media · Actions like/comment (optimistic)
News Feed Architecture

D — Data Model

Normalized state
// Normalize to avoid duplication & simplify updates.
{
  posts: {
    byId: { "p1": { id, authorId, text, mediaUrl, likeCount, likedByMe } },
    feedOrder: ["p1", "p2", "p3"],   // Display order
    nextCursor: "cursor_abc"            // cho pagination
  },
  users: { byId: { "u1": { id, name, avatar } } }
}

I — Interface / API

API contract
GET  /feed?cursor=<c>&limit=10   → { posts, nextCursor }
POST /posts        { text, media }  → { post }
POST /posts/:id/like                → { likeCount, likedByMe }
GET  /feed/updates  (SSE)           → pushes "There are N new posts."

O — Optimizations (score points!)

  • Smooth scrolling: virtualization keeps the DOM lightweight even with an infinite feed.
  • Load more: IntersectionObserver on the last sentinel in the list → fetch the next page.
  • Image: lazy-load, use srcset/responsive, and blur placeholder to avoid CLS.
  • Instant interaction: optimistic UI for likes/comments, rollback on error.
  • New post: SSE signals "new post" → show pill, do not auto-insert to avoid layout shift.
  • Offline/error: display old cache + skeleton, retry with backoff.
  • Code splitting: separate PostComposer and media viewer from the main bundle.
  • A11y: aria-live for new posts, keyboard navigation for actions.
How to "score points" in the O part

Don’t list everything. Pick 2–3 optimizations most relevant to the finalized requirements (e.g., because smoothness on mobile is needed → virtualization + responsive images) and dive deep into them. Depth over breadth.

🧩Quick Check

In the News Feed, why should we show a "X new posts" pill instead of automatically inserting new posts at the top of the list?

37 — PRACTICE

Interview Checklist — Print and Review Quickly

A set of "reflexes" to ensure you don't miss anything in the interview. Practice until it becomes second nature.

60-Minute Agenda

R · 5' A · 12' D · 8' I · 8' O · 12' + Q&A
Suggested time allocation

Always mention "trade-off" questions.

⚖️

Performance vs Complexity

Which optimizations are worth the effort? Measure before optimizing.

⚖️

CSR vs SSR

Balance between SEO/front-end performance and server cost/complexity.

⚖️

Real-time vs Simple

WebSocket is powerful but complex; polling is sufficient for many cases.

⚖️

Cache vs Freshness

Stale-while-revalidate balances both.

Signs of a strong candidate

(1) Ask clarifying questions before solving. (2) Draw clear diagrams. (3) Always mention trade-offs, never say "always use X". (4) Proactively address a11y, error handling, and performance. (5) Manage time wisely, know when to dive deep.

Comprehensive flashcard set

Flashcards6
38 — PRACTICE

Question bank summary

Mix multiple topics to test overall knowledge. Answer first, then view the explanation. Try to explain "why" yourself before clicking.

🧩Quick Check

The biggest issue with pure CSR for a marketing website is what?

🧩Quick Check

Which technique is most effective at reducing initial JS load time for large apps?

🧩Quick Check

GraphQL best solves the problem of over-fetching and under-fetching data in REST APIs.

🧩Quick Check

Where should the selected filter and tab be stored so that sharing the link and the back/forward buttons work correctly?

🧩Quick Check

The lightest and most suitable mechanism for displaying push notifications from the server is?

🧩Quick Check

Which attribute is used to announce dynamic content (e.g., "added to cart") to screen readers?

🧩Quick Check

The most secure way to store session tokens against XSS is?

🧩Quick Check

Reading `element.offsetHeight` immediately after changing the styles of multiple elements will cause what?

🧩Quick Check

Between Promise.then and setTimeout(fn, 0), which runs first?

🧩Quick Check

According to the testing pyramid, unit tests should make up the largest number of tests.

🧩Quick Check

The core difference between "lab data" and "field data" is what?

🧩Quick Check

Tree-shaking requires ES module syntax (import/export) to work effectively, as it relies on static analysis to determine which exports are used and which can be removed.

🧩Quick Check

The most common cause of CLS (Cumulative Layout Shift) related to images is missing explicit width and height attributes on image elements.

🧩Quick Check

What is the role of a Service Worker?

🧩Quick Check

Why should chat use WebSocket instead of SSE?

In conclusion

FE System Design has no single correct answer. Practice the mindset of: clarify → structure → trade-offs → deep dive. Revisit the diagrams and flashcards here regularly; spaced repetition is the best way to retain knowledge. Wishing you a successful interview! 🚀