A structured weekend checklist for mastering large-scale UI system design interviews. Focus: Think in systems, not just code.
- How to Structure Your Answer
- Rendering Strategy
- Data Fetching Strategy
- State Management
- Performance Optimization
- Caching Strategy
- Scalability Considerations
- Error Handling & Reliability
- Security
- Accessibility
- Testing
- Real Product Design Examples
- Example Answer Flow
- Must-Know Concepts for SDE-2
Interviewers at SDE-2 level want to see structured thinking, not just code. Always lead with requirements before diving into implementation.
Answer: What does the UI do?
- Key user flows (e.g., view feed, like/comment, infinite scroll, notifications)
- Core features in scope vs. out of scope
Answer: How well does it do it?
| Concern | Example Target |
|---|---|
| Performance | Page load < 2s, TTI < 3s |
| Scalability | Handle 1M+ concurrent users |
| SEO | Crawlable content for public pages |
| Offline Support | Works without network (e.g., Gmail offline) |
| Accessibility | WCAG 2.1 AA compliance |
Interview tip: Always clarify both functional and non-functional requirements before designing. This signals maturity.
One of the most commonly tested topics. Know when to use each strategy and why.
The browser downloads a minimal HTML shell and renders everything via JavaScript.
- Best for: Highly interactive apps with no SEO needs
- Examples: Dashboards, Gmail, Trello, admin panels
- Pros: Rich interactivity, seamless navigation after initial load
- Cons: Slower first contentful paint, poor SEO without extra work
The server renders full HTML on each request and sends it to the browser.
- Best for: SEO-critical, content-heavy pages
- Examples: E-commerce product pages, blogs, marketing sites
- Pros: Fast first paint, great SEO, works without JS
- Cons: Higher server load, slower TTFB under load
HTML is pre-rendered at build time and served as static files.
- Best for: Rarely-changing content
- Examples: Documentation sites, landing pages, portfolios
- Pros: Fastest possible load, CDN-friendly, zero server cost
- Cons: Stale content between builds, long build times at scale
Static pages are regenerated in the background at configurable intervals.
- Best for: Large catalogs that change occasionally
- Examples: Product pages, news archives
- Pros: Combines SSG speed with near-fresh data
- Cons: Users may briefly see stale content
The server streams HTML progressively; only interactive components are hydrated.
- Best for: Complex pages where parts load faster than others
- Examples: Next.js App Router with React Server Components
- Pros: Faster perceived load, reduced JS bundle
- Cons: Added complexity, framework-dependent
Match your data fetching pattern to the freshness and latency requirements of your data.
Client repeatedly fetches data on a fixed interval.
setInterval(() => fetch('/api/notifications'), 5000);- Use when: Near-real-time is acceptable, WebSocket setup is overkill
- Examples: Notification counts, dashboard metrics
Client makes a request; server holds it open until new data is available.
- Use when: You need real-time feel without WebSockets
- Examples: Legacy chat apps, simple notification systems
Full-duplex, persistent connection between client and server.
const ws = new WebSocket('wss://api.example.com/chat');
ws.onmessage = (event) => updateUI(JSON.parse(event.data));- Use when: True bidirectional real-time communication is required
- Examples: Chat, collaborative editors (Google Docs), multiplayer games, trading dashboards
Server pushes a one-way stream of updates to the client over HTTP.
const es = new EventSource('/api/live-scores');
es.onmessage = (event) => updateScore(event.data);- Use when: Server → client updates only (no client → server stream needed)
- Examples: Live sports scores, stock prices, news feeds
| Method | Direction | Protocol | Best For |
|---|---|---|---|
| Polling | Client → Server | HTTP | Infrequent updates |
| Long Polling | Client → Server | HTTP | Near-real-time, simple |
| WebSockets | Bidirectional | WS | Chat, games, collaboration |
| SSE | Server → Client | HTTP | Live feeds, push notifications |
Think about where state lives and who owns it — not just which library to use.
Component-level state for UI-only concerns.
const [isOpen, setIsOpen] = useState(false);- Use for: Modals, dropdowns, form inputs, toggles
Application-wide state shared across many components.
// Zustand example
const useStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
}));- Tools: Redux (large apps), Zustand (lightweight), React Context (simple cases)
- Use for: Auth session, theme, user profile, shopping cart
Remote data with its own lifecycle: loading, stale, error, revalidating.
// React Query example
const { data, isLoading } = useQuery(['posts'], fetchPosts, {
staleTime: 60 * 1000, // 1 minute
});- Tools: React Query, SWR
- Handles automatically: Caching, background refetch, deduplication, pagination
Interview tip: Distinguish server state from global state. Fetched data is not the same as app state — treat them separately.
This section differentiates SDE-2 candidates. Know all three layers.
Reduce the amount of JavaScript the browser must download and parse.
- Code splitting: Break app into async chunks loaded on demand
- Lazy loading: Defer non-critical components
const ProductPage = React.lazy(() => import('./ProductPage'));- Tree shaking: Remove unused exports at build time
- Vendor splitting: Separate third-party libraries into their own chunk (changes less often → better cache hits)
Reduce network payload for images and static assets.
- Image formats: Prefer WebP or AVIF over PNG/JPEG (30–50% smaller)
- Responsive images: Use
srcsetto serve size-appropriate images - CDN: Serve assets from edge nodes close to the user
- Compression: Enable gzip or Brotli on the server
Keep the UI fast after the page loads.
- Virtualization: Render only visible rows in long lists
import { FixedSizeList } from 'react-window';
// Renders only ~10 visible items even for 100,000-row lists- Memoization: Prevent unnecessary re-renders
const MemoizedCard = React.memo(Card);
const sortedList = useMemo(() => sort(items), [items]);
const handleClick = useCallback(() => doSomething(id), [id]);- Debounce / Throttle: Limit high-frequency event handlers (search input, scroll, resize)
Optimize what the browser does before first paint.
<link rel="preload">for critical fonts and above-the-fold images<link rel="prefetch">for likely-next-page resourcesasync/deferon non-critical scripts to unblock HTML parsing- Minimize render-blocking CSS; inline critical CSS
Always discuss multiple caching layers — not just the application layer.
Controls how long the browser stores static assets locally.
Cache-Control: public, max-age=31536000, immutable // for versioned assets
Cache-Control: no-cache // for HTML pagesCaches responses at edge servers geographically close to users.
- Dramatically reduces latency for global users
- Cache-bust by versioning asset filenames (e.g.,
main.abc123.js)
Keeps fetched data in memory and serves it instantly on repeat visits while revalidating in the background.
useQuery(['user', userId], fetchUser, {
staleTime: 5 * 60 * 1000, // serve from cache for 5 minutes
cacheTime: 10 * 60 * 1000, // keep in memory for 10 minutes after unmount
});Persist data across sessions in the browser.
- LocalStorage: Small key-value data (tokens, preferences) — synchronous, 5MB limit
- IndexedDB: Large structured data — asynchronous, good for offline-first apps
- Examples: Gmail offline, Google Docs offline draft storage
Interview tip: Mention cache invalidation strategy — what happens when data changes? Stale-while-revalidate, TTL, manual invalidation on mutation.
How does the UI hold up as data volume and user count grow?
Load data in pages instead of all at once.
- Cursor-based pagination is preferred over offset for large, frequently-updated datasets (avoids duplicate/missing items)
- Infinite scroll with virtualization = best of both worlds for feeds
Render only the DOM nodes visible in the viewport.
// 100,000-row table — only ~20 DOM nodes at any time
<FixedSizeList height={600} itemCount={100000} itemSize={50}>
{Row}
</FixedSizeList>Split a large frontend into independently deployable modules owned by separate teams.
- Use when: Multiple teams, large codebase, independent deployment cycles
- Examples: Amazon, Spotify, IKEA
- Approaches: Module Federation (Webpack 5), iframes, Web Components
Ship code to production without exposing it to users yet; roll out gradually.
- Enables A/B testing, canary releases, and instant rollback
- Tools: LaunchDarkly, Unleash, custom flag service
Production-grade UIs anticipate failure.
Automatically retry failed requests with exponential backoff.
async function fetchWithRetry(url, retries = 3, delay = 500) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(res => setTimeout(res, delay * 2 ** i));
}
}
}Catch rendering errors in a component subtree and show a fallback UI.
<ErrorBoundary fallback={<ErrorMessage />}>
<FeatureComponent />
</ErrorBoundary>- Show skeleton screens while loading (better UX than spinners for layout-heavy pages)
- Show cached/stale data with a "refresh" prompt rather than a blank screen
- Log errors to services like Sentry or Datadog RUM
- Track Core Web Vitals (LCP, FID/INP, CLS) in production
- Set up alerts for error rate spikes
Mention these briefly but confidently.
- XSS Prevention: Never inject raw HTML; use React's JSX escaping; set
Content-Security-Policyheaders - CSP (Content Security Policy): Whitelist trusted script/style sources to block injected malicious scripts
- Input Sanitization: Strip or escape dangerous characters before rendering user-generated content
- Secure Cookies: Use
HttpOnly,Secure, andSameSite=Strictflags on auth cookies - Auth Tokens: Store tokens in memory or
HttpOnlycookies — never inlocalStorage(vulnerable to XSS)
Cover this briefly — it signals quality engineering.
- Semantic HTML: Use
<button>,<nav>,<main>,<h1>correctly so screen readers understand structure - ARIA: Add
aria-label,aria-expanded,roleattributes where semantic HTML is insufficient - Keyboard Navigation: All interactive elements must be reachable and operable via keyboard
- Color Contrast: Meet WCAG AA (4.5:1 ratio for normal text)
- Focus Management: Restore focus after modals close; trap focus within open modals
Show you think about reliability, not just features.
| Type | What It Tests | Tools |
|---|---|---|
| Unit | Individual functions and components in isolation | Jest, Vitest |
| Integration | Component interactions and data flow | React Testing Library |
| E2E | Full user flows in a real browser | Cypress, Playwright |
| Visual Regression | UI appearance across changes | Percy, Chromatic |
// Integration test example
test('user can submit a comment', async () => {
render(<CommentForm />);
await userEvent.type(screen.getByRole('textbox'), 'Great post!');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(await screen.findByText('Great post!')).toBeInTheDocument();
});Practice these four. Most SDE-2 frontend design questions are variations of them.
Key topics to cover:
- Infinite scroll + cursor-based pagination
- Feed virtualization with
react-window - Optimistic UI for likes/retweets
- Real-time updates via WebSockets or SSE
- Cache strategy: React Query with stale-while-revalidate
- Rendering: CSR with SSR for initial load (hybrid)
Key topics to cover:
- WebSockets for real-time sync
- Operational Transforms (OT) or CRDTs for conflict resolution
- Offline support with IndexedDB + sync queue
- Autosave with debounce
- Presence indicators (who else is editing)
Key topics to cover:
- Adaptive bitrate streaming (HLS / DASH) — quality adjusts to bandwidth
- CDN for video delivery at global scale
- Lazy loading of video player and recommendations
- Progressive loading: show thumbnail → load player → buffer video
- Pre-fetching next episode
Key topics to cover:
- WebSockets for real-time message delivery
- Optimistic UI: show message immediately, confirm on server ack
- Message ordering with sequence numbers or timestamps
- Retry logic for failed sends
- Infinite scroll (upward) for history
- Read receipts and typing indicators
When asked "Design a Twitter-like news feed", structure your answer in this order:
1. Requirements
├── Functional: view feed, post tweet, like, retweet, infinite scroll
└── Non-functional: < 2s load, 1M users, real-time updates
2. High-Level Architecture
└── CDN → Load Balancer → API Gateway → Services → DB
3. Rendering Strategy
└── SSR for first load (SEO + speed) → CSR for interactions
4. Data Fetching
└── Initial: SSR fetch → Client: WebSocket for live updates
5. Caching
└── CDN for assets → React Query for feed data (stale-while-revalidate)
6. Performance Optimization
└── Virtualized feed list → Lazy-loaded images → Code splitting
7. Scalability
└── Cursor pagination → Feature flags for new features
8. Edge Cases
└── Offline mode → Error boundaries → Retry on failure
Tip: Speak out loud as you draw. Interviewers care as much about how you think as what you design.
Be able to explain each of these clearly and concisely — not just name them.
| Concept | What You Should Know |
|---|---|
| React Reconciliation | How React diffs the virtual DOM and determines minimal DOM updates |
| React Fiber | React's incremental rendering engine; enables time-slicing and Suspense |
| Virtualization | Rendering only visible DOM nodes to handle massive lists efficiently |
| Code Splitting | Breaking bundles into async chunks loaded on demand via dynamic import() |
| Caching Layers | Browser cache → CDN → Application cache → LocalStorage/IndexedDB |
| WebSockets vs. Polling | WS = persistent bidirectional; Polling = periodic HTTP; SSE = server push |
| CDN | Edge servers that serve assets from locations geographically close to users |
| Service Workers | Background scripts enabling offline support, push notifications, and cache control |
| Critical Rendering Path | The sequence browser follows to render a page; optimize by reducing blocking resources |
| Bundle Optimization | Tree shaking, code splitting, vendor chunking, lazy loading to reduce JS payload |
Good luck — you've got this. 🚀