The key idea
Leave behind a small set of concrete artifacts: a scoped flow, ownership diagram, data model, API contract, and a justified deep dive.
Start with five answers
- Who is the user, and what is the one action they must complete?
- Which features are required now, and which are excluded?
- Which devices, browsers, locales, and accessibility needs matter?
- How much data and traffic do we expect, and how fresh must the UI be?
- What would count as failure: slow interaction, lost work, stale information, or unauthorized access?
When a number is unavailable, state an assumption and explain what changes if it is wrong. “I will design for thousands of feed items, but only a small window rendered at once” is more useful than an unsupported claim of unlimited scale.
Keep a RADIO checklist beside the diagram
| Step | Leave something concrete |
|---|---|
| Requirements | Primary journey, scope, assumptions, and success criteria. |
| Architecture | Responsible components and arrows showing a read and a write. |
| Data model | Entities, IDs, relationships, state owners, and lifecycle. |
| Interface | Request and response examples, pagination, errors, and permissions. |
| Optimizations | Two important risks, alternatives, and a validation method. |
For a 45-minute rehearsal, try 5 minutes on scope, 8 on architecture, 7 on data, 8 on interfaces, 12 on deep dives, and 5 on review. This is a practice aid; follow the interviewer when priorities change.
Specify a contract, not just an endpoint
For a feed, model a post's stable ID, author reference, content, media, timestamps, and viewer permissions. Store list order separately from records if several views reuse them. Define the cursor's ordering guarantees with the server; a cursor alone does not guarantee stable pagination.
Illustrative feed response
typescripttype FeedPage = {
items: Array<{
id: string;
authorId: string;
text: string;
updatedAt: string;
canEdit: boolean;
}>;
nextCursor: string | null;
};
// GET /api/feed?limit=20&cursor=opaque-token
// Authentication and visibility are checked on the server.
// A null nextCursor means there is no next page.- Name validation, authentication, authorization, conflict, rate-limit, and temporary failure responses.
- For mutations, define the canonical server result, optimistic reconciliation, and duplicate-request policy.
Give each kind of state a home
| State | Typical owner |
|---|---|
| Posts and permissions | Server, mirrored in an account-scoped query cache. |
| Filters, sorting, selected resource | URL when sharing and browser history should preserve them. |
| Open menu, focus, input draft | Local UI state; persist a draft only when the product requires it. |
| Pending mutation | Explicit operation state with a reconciliation or retry path. |
Define cache identity, freshness, invalidation after writes, and logout cleanup. private limits shared-cache storage; no-store disallows storage by HTTP caches. Application caches and local storage need their own policy. Cancel obsolete requests and guard against results from an older query replacing the current one.
Choose rendering and transport for the journey
Discoverable content
Server-render or prerender public product and article content when indexing and an early useful page matter. Keep account-specific data isolated.
Interactive application
Client rendering can suit authenticated workflows. Budget JavaScript and network waterfalls; rendering a shell does not make the data ready.
Freshness
Polling is simple when some delay is acceptable. Server-sent events deliver server-to-client updates. WebSockets suit bidirectional live interaction, with more lifecycle management.
For any live transport, define reconnect backoff, event IDs, deduplication, and how to recover missed changes. Pause unnecessary work when appropriate and cap retained data. Explain a fallback if the live channel fails.
Review the states the screenshot does not show
- Initial loading: reserve useful space and show progress without blocking unrelated controls.
- Empty results: distinguish no content from an invalid filter or failed request.
- Refreshing: retain useful data when safe, and make stale or pending status clear.
- Failure: preserve input, give an actionable message, and avoid blindly replaying a write.
- Offline: state what can be viewed or edited and when pending work becomes confirmed.
- Accessibility: native semantics, labels, keyboard operation, focus management, and understandable status announcements.
- Responsive and international use: zoom, narrow layouts, long labels, right-to-left text, localized dates and numbers.
For autocomplete, describe selection with arrow keys, Enter, and Escape as well as pointer interaction. Decide what happens to focus when results change. A live region should inform the user without announcing every keystroke unnecessarily.
Set measurable performance goals
Current good Core Web Vitals thresholds are LCP at most 2.5 seconds, INP at most 200 milliseconds, and CLS at most 0.1, assessed at the 75th percentile of field visits and segmented by mobile and desktop. These measure loading, responsiveness, and visual stability; add a product-specific metric such as time until search results become usable.
- Inspect requests, JavaScript size, image dimensions, main-thread work, and retained memory before choosing an optimization.
- Use realistic lists, images, low-end devices, and slow connections when validating.
- Combine laboratory checks with real-user monitoring. Lighthouse cannot directly measure interaction-based INP during a page-load run.
Draw the trust boundary
- Authenticate identity and authorize the specific resource on the server; never trust a client-supplied role or hidden button.
- Keep service secrets server-side and validate input at the API boundary.
- Render ordinary text through safe framework output; sanitize intentionally rendered HTML and validate user-supplied URLs.
- Define cookie/session and cross-site request protections for the chosen authentication model.
- Avoid shared caching of private responses, and decide how sensitive drafts and account caches are cleared.
- Collect the minimum diagnostic data; redact tokens, personal content, and unnecessary identifiers.
Close with verification and unanswered risks
Test transformations and reducers, component keyboard behavior, API error and permission contracts, and the critical end-to-end journey. Add cases for racing requests, reconnection, duplicate writes, expired sessions, and back navigation. Use representative data sizes for performance checks.
Observe route errors, API latency and failures, Web Vitals, and completed user actions. Correlate failures with a release and request ID, while protecting private data. Name one alert that would reveal a broken journey and one fallback that keeps the application usable.
Before you finish
Can you trace the primary action from UI to server and back? Explain why the chosen tradeoff fits the requirements? Show what the user sees when it fails? Name the next experiment for the largest remaining uncertainty?
Sources & further reading
Original explanations and examples by FineFrontend, informed by the guide and primary references below.
- GreatFrontEnd: System design cheatsheet (opens in a new tab)
Reference for the RADIO checklist and interview review. The contracts, examples, and operational checks here are original.
- MDN: Cache-Control (opens in a new tab)
The distinction between private caches, revalidation, and no-store.
- MDN: AbortController (opens in a new tab)
Cancellation of fetch requests and response processing.
- WAI: Combobox pattern (opens in a new tab)
Keyboard interaction, focus, and accessible state for autocomplete interfaces.
- web.dev: Web Vitals (opens in a new tab)
Current Core Web Vitals thresholds, field percentiles, and the limits of laboratory measurements.
- OWASP: Authorization (opens in a new tab)
Server-side permission checks and authorization tests.
- OWASP: Cross-site scripting prevention (opens in a new tab)
Safe text rendering, HTML sanitization, and security boundaries in frameworks.
- MDN: Using server-sent events (opens in a new tab)
One-way event delivery, event identifiers, and reconnection behavior.