Reading a design is faster than inventing one under pressure. These four are written the way a strong candidate would talk through them, compressed; practise expanding each section out loud and swapping in the numbers an interviewer gives you.
Requirements asked: ~50k SKUs, 500k monthly visitors, SEO critical, prices change hourly, stock changes in minutes, guest and logged-in carts, EU locales and currencies, WCAG 2.2 AA, payments via a provider.
Architecture: CDN in front; Nuxt with route rules: / prerendered, /c/** and /p/** swr 300 keyed by URL with Vary: Accept-Language, /search SSR uncached over a cached search function, /cart and /checkout SSR private, /account/** SPA. Nitro as BFF over the product database (Postgres with a read replica), Meilisearch for search, the payment provider called only from the server, a CMS for editorial content with webhooks that clear swr entries.
Data and API: products, variants, prices (per currency, valid-from), stock, categories, carts, orders. Endpoints: GET /api/catalog/products?category&cursor&limit&sort (cursor pagination, cached 300 s), GET /api/pdp/:slug (BFF aggregation: product, price, review summary, related), GET /api/stock/:sku (private, no-store, or SSE for live stock), POST /api/cart/items, POST /api/checkout with Idempotency-Key, webhooks from payments into an outbox.
State placement: filters and page in the URL; cart in a Pinia store hydrated from a cookie holding item ids and quantities; product data in the useFetch cache; session cookie via nuxt-auth-utils; locale and currency cookies; live stock via SSE into useState.
Hard part — search and autocomplete: debounced, cancellable suggestions from /api/search/suggest (Meilisearch prefix search, cachedFunction 30 s per normalised query); full results page server-rendered for crawlable popular queries; combobox ARIA pattern; facets in the URL; indexing task triggered by product changes through the outbox.
Cross-cutting: LCP image with @nuxt/image preload and fetchpriority, fonts self-hosted with metric fallbacks, reviews lazy-hydrated, Lighthouse budgets in CI; axe and keyboard tests on PDP and checkout; strict CSP with nonces, HttpOnly sessions, validation with shared schemas; hreflang and canonical per locale; Sentry plus RUM by route.
Trade-offs and scale: swr means a price can be five minutes stale, acceptable with server-side revalidation at checkout; SSR for search costs CPU, mitigated by caching hot queries; at 10× traffic the catalogue moves to isr at the CDN and stock to a dedicated realtime channel; at 100× the search index is sharded and the BFF gets its own autoscaling group.
Requirements asked: internal users after login, 200 concurrent viewers, metrics updating every few seconds, tables of 100k rows, filters and saved views, no SEO, WCAG AA, role-based access to tenants' data.
Architecture: /dashboard/** as an SPA (ssr: false) behind auth middleware and server-side session checks; Nitro BFF over a metrics store (time-series database or Postgres with aggregates) and a Redis pub/sub for live updates; SSE endpoint per dashboard for pushes; static shell on the CDN.
Data and API: GET /api/metrics?range&granularity&filters (validated, bounded, cachedFunction per key 5 s), GET /api/metrics/stream (SSE with versioned deltas), GET/PUT /api/views (saved views per user), GET /api/export (streaming CSV).
State placement: filters and range in the URL (shareable dashboards); query results in TanStack Query with staleTime and background refetch on focus; live deltas applied to the query cache by version; saved views in Pinia; dense/comfortable layout in a cookie.
Hard part — 100k-row tables and live updates: virtualised rows (fixed height), sorting and grouping computed in a Web Worker, shallowRef data, batched application of SSE deltas (coalesce 50 ms), pause when the tab is hidden, resume with "since version" on reconnect; column virtualisation for wide tables.
Cross-cutting: INP budget under 200 ms verified in Playwright with the Performance API; keyboard navigation for grids (roving tabindex), live regions for alerts; per-tenant authorisation in every query; audit logging of exports.
Trade-offs and scale: SPA gives up first-paint HTML, acceptable for logged-in internal users; SSE over WebSockets because traffic is one-directional and serverless-friendly; with several instances the SSE fan-out needs the Redis backplane; at 10× viewers, precomputed aggregates and a dedicated push service.
Requirements asked: tenants on subdomains with their own branding, a public marketing site plus documentation in three languages, a logged-in app with roles, GDPR, uptime commitments.
Architecture: one monorepo, two Nuxt apps sharing layers (UI tokens and primitives, auth, analytics): www (prerendered marketing and Nuxt Content docs, per-locale routes, swr for CMS-driven pages, OG images, sitemap) on the edge; app (SSR shell plus SPA sections) regional, close to the database. Tenant resolution in server middleware from the host; tenant tokens applied as data-brand; row-level security in Postgres keyed by tenant.
Data and API: tenants, users, memberships(role), domain tables with tenant_id; sessions carry tenantId and role; every cache key includes the tenant; feature flags per tenant.
State placement: tenant and brand from the request (server) into useState; user and role from the session; UI preferences in cookies; domain data in the query cache.
Hard part — tenancy and permissions: middleware resolves and validates the tenant, rejects unknown hosts, sets event.context; authorisation helpers check membership and role per object; tests request the same URL as two tenants; invitations and role changes bump session versions.
Cross-cutting: i18n with hreflang and lazy messages; docs search built at generate time; audit logs; data export and deletion endpoints for GDPR; per-tenant rate limits; CSP per app.
Trade-offs and scale: shared layers speed both apps but couple release cadence, mitigated by versioned layers in the workspace; RLS adds safety at the cost of query planning care; at many tenants, per-tenant caches fragment and hot tenants need their own limits.
Requirements asked: users upload documents and images up to 500 MB, must see progress, files are scanned and converted (thumbnails, PDF previews), then shared via links with expiry, on serverless hosting.
Architecture: presigned multipart uploads directly to R2/S3 from the browser; the API records intent and completion; an outbox-driven task pipeline (Nitro tasks on platform cron, or a queue) scans, converts and writes derived files; progress and status over SSE or polling of a job record; delivery through signed URLs from a separate files origin behind the CDN.
Data and API: files(id, owner, tenant, key, size, type, status, version), jobs(id, fileId, type, attempts, state), shares(token, fileId, expiresAt, permissions); POST /api/files/presign, POST /api/files/:id/complete, GET /api/files/:id/events (SSE), POST /api/shares, GET /files/:token (validates, redirects to a short-lived signed URL).
State placement: upload progress in component state from the XMLHttpRequest/fetch progress events; file list in the query cache invalidated by SSE status events; share settings in the URL and the API.
Hard part — reliability and safety: idempotent jobs by file id and version, retries with backoff, dead letters with alerts; magic-byte validation and re-encoding; malware scanning as a job; per-user quotas; resumable multipart uploads; never buffering bytes through the server.
Cross-cutting: accessible progress (role="progressbar", live region), keyboard-operable drag-and-drop with a file input fallback (WCAG 2.5.7), size and type errors as form errors; nosniff and Content-Disposition on delivery; signed URL expiry; audit of shares.
Trade-offs and scale: presigned uploads remove the server from the data path at the cost of a two-step protocol; SSE for progress needs a backplane with several instances; at scale the processing moves to a dedicated worker pool with a real queue and the files origin gets its own CDN rules.
The skeleton you should be able to fill in for any prompt, in text:
Requirements → Scope → Architecture (rendering per route, BFF, stores, third parties) → Data & API →
State placement → Hard part (mechanism, UI, perf, trade-offs) → Cross-cutting (perf, a11y, security, i18n, ops) →
Trade-offs & scale (what I accepted, when I would change it)
Interviewers change one constraint ("actually, no SEO" or "the users are on 3G in rural areas") to see whether the design moves with it. Rehearse the reasoning, and when a constraint changes, say which rows of the tables change.
"Let me confirm the shape: tens of thousands of products, SEO critical, prices changing hourly, guest and logged-in carts, several locales. Then: a CDN in front of Nuxt, with rendering per route group: the homepage prerendered, category and product pages swr for five minutes keyed by URL and locale, search server-rendered over cached search functions, cart and checkout server-rendered privately, the account area an SPA behind auth. Nitro as the backend-for-frontend aggregating product, price and review data into one payload, calling the payment provider only from the server, and clearing caches from CMS webhooks. Filters live in the URL, the cart in a Pinia store backed by a cookie, product data in the fetch cache, the session in an HttpOnly cookie. The hard part I would go deep on is search: debounced, cancellable suggestions against Meilisearch with an accessible combobox, and a crawlable results page. Then performance budgets, accessibility tests on checkout, CSP and validation, hreflang per locale, and monitoring by route. The trade-off I am accepting is five minutes of price staleness, revalidated at checkout."
Nothing yet. Notes filed here show up on this page and the section overview.
Rendering and data architecture
Deciding rendering per route with a criteria table, the caching layers from browser to database, server-side aggregation versus client waterfalls, realtime and auth architecture, multi-tenant theming and i18n routing, search, edge versus region placement, resilience, observability as a design input, and migration paths.
Decision records and trade-offs
Writing architecture decision records, running lightweight RFCs, evaluating libraries and services, build versus buy, reversible versus one-way decisions, planning migrations as strangler moves, managing technical debt with a budget, and communicating trade-offs to product and leadership.