Design prompt: the ExamHall candidate app
Four guarantees: the paper is sealed and local before 09:00, no answer is lost or overwritten, outages are survivable, and every locale saves one value.
Predict first
Before you read: predict
ECON-201 opens at 09:00:00 for 2,400 candidates, and its paper is 1.8 MB. In rehearsals every candidate pressed Start within 4 s of it appearing. The team wants three things: at most 100 MB of paper sent in any one second between 09:00:00 and 09:01:00; every candidate reading question 1 by 09:00:10; and no candidate able to read the paper before 09:00:00.
Which plan meets all three?
The 30-second version
Four end-to-end guarantees shape the candidate app. Move the paper's bytes before 09:00 as an encrypted copy, so the opening second carries only a small key, released by the server's clock. Save every edit locally first: one row per question, with a rising revision the server keeps only if higher. Judge offline by failed saves. Normalize typed numbers per locale.
Four guarantees: the paper is sealed and local before 09:00, no answer is lost or overwritten, outages are survivable, and every locale saves one value.
How it works
Step 1 of 14
In the kitchen
- a store selling downloads only at launch: every full download in one minute → fetching the paper at Start: the herd (step 1)
Analogy: A video game pre-loaded before its launch
Players download a large game days early, still encrypted; at the release hour, kept on the store's servers, each launcher fetches a small key.
| At a game launch | In the candidate app |
|---|---|
| the encrypted files downloaded in the days before | the sealed paper downloaded during check-in (steps 2–3) |
| each player's download starting when that player chooses | each client's own moment, never 09:00 itself (step 2) |
| the small key fetched at the release hour | the one request the opening second carries (step 3) |
| moving the PC's clock forward unlocks nothing | the server's clock opens the paper (step 4) |
| a store selling downloads only at launch: every full download in one minute | fetching the paper at Start: the herd (step 1) |
In real terms: the opening instant moves a few bytes to every client; the megabytes moved earlier, at moments each client chose.
Breaks down when: a player who waits loses nothing, while a candidate whose paper arrives late loses exam minutes. A game launch also has no answers to keep, so steps 5–12 have no counterpart in it.
Contrasting case: a concert's ticket sale at 10:00. What is sold cannot be delivered in advance, so the herd stays at the opening and needs a waiting room (sc-exam-start-spike).
Examples, run for real
4.1 ExamHall EXH-2030 → MR !2330: two drafts received out of order
After a reload, a candidate's Q9 showed an older draft. Its autosaves of revision 1 and revision 2 were in flight together on separate connections, and the server received revision 2 first. The server stored whichever save arrived last. MR !2330 is step 7's fix.
EXH-2030 last arrival wins · received rev 2, then rev 1 · Q9 stores rev 1
Arrival order is not edit order, so a server that keeps the last arrival can keep the older draft (step 7).
4.2 Proof domain, e-commerce checkout: a flash sale's prices, sealed before noon
A shop sends its 50 sale prices to every open checkout page at 11:52, encrypted. At 12:00 on the shop's server clock, each page fetches the 32-byte key.
checkout 11:52 · browser holds 1451 bytes of ciphertext · read without the key: Unsupported state or unable to authenticate data
checkout 12:00 · key fetched: 32 bytes · read with the key: 50 prices
Before the key arrives, the browser holds bytes it cannot read: GCM refuses a read with the wrong key. The noon second moves 32 bytes per page (steps 2–4).
4.3 The board for "Design the candidate-facing exam page" (worked, not executed)
| Risk | On the board | Mechanism owner |
|---|---|---|
| the 09:00 herd | the opening second carries no paper bytes (steps 1–4) | this lesson; the anchored clock: fsd-clock-sync-timers |
| a lost or overwritten answer | every edit survives a reload and a late arrival (steps 5–8) | this lesson; the outbox and replay: fsd-client-retry-resilience |
| minutes offline | answering continues, and queued saves still arrive (steps 9–12) | this lesson |
| locale | one stored value per typed answer (steps 13–14) | this lesson; catalogs and plurals: fsd-i18n-architecture |
Try it
Answer first, then check. Nothing is saved.
Question 1 of 5
Q7's autosave sends PUT /answers/q7 with the draft's text, and the server queues each save as a job that writes the text it carries. A job that hits a database timeout is retried 30 s later. The page loaded Q7 at draft 2; the candidate then saves drafts 3, 4 and 5 at 88:10, 88:20 and 88:30. In run 1 the job for draft 3 hits a timeout; in run 2 the job for draft 5 does. Today, run 1 ends with draft 3 stored.
Which change makes Q7 store draft 5 in both runs?
Question 2 of 5
A candidate's answers must keep reaching the server through Wi-Fi or building outages of a few minutes. Four rules for the candidate app's offline handling are on the design board.
Which rule should the design keep?
Question 3 of 5
Q12 asks for a price elasticity to one decimal place, and its key is 3.5. Three candidates type ٣٫٥ on /ar-EG/exam, 3,5 on /fr/exam and 3.5 on /en/exam. Today the answer box saves parseFloat(input.value), and the grader compares the saved number with the key.
Which change saves 3.5 for all three?
Question 4 of 5
Every client keeps its outbox like this:
During a 90-second loss of the hall's internet link, the edits on each of the 2,400 clients were, in order, q3, q3, q7, q3, q7, q12, q7, q3, q12, q12, q3, q7, and the save of q3's first draft was in flight when the link failed. When the link returns, every client runs flush().
How many save requests do the flushes send in all?
const outbox = await openStore('outbox', { keyPath: 'q' });async function onEdit(q, text) { await outbox.put({ q, rev: ++revs[q], text }); trySend(q); }async function flush() { for (const row of await outbox.getAll()) if ((await send(row)).ok) await outbox.delete(row.q);}Question 5 of 5
ECON-201 ends at 90:00 of attempt time, and the server stores a save received up to 60 s after the deadline. In a rehearsal, the hall's internet link is down from 88:30 to 90:40, and the candidate writes five drafts across Q37, Q39 and Q40 during it. Every edit typed in AnswerEditor is first written to an IndexedDB outbox row, and flush() sends each row; an accepted save deletes it. The app:
clock is the server-anchored clock, and every() returns a function that stops the repeat. The team must choose where clock.every(5000, flush) runs.
Which home gets the latest draft of all three questions stored by 95:00, both in a run with no reload and in a run where the candidate reloads the page at 90:20?
function App() { const [route, setRoute] = useState('exam'); useEffect(() => clock.every(1000, (now) => { if (deadline - now <= 0) setRoute('time-up'); }), []); return route === 'exam' ? <AnswerEditor /> : <TimeUp />;}Go deeper
Pro tip
- In the first minutes of the prompt, name the two riskiest parts, the 09:00 herd and never losing an answer, and say you will open them before any component tree. When: a 45-minute frontend round on this product (the deep-dive choice itself is
fsd-radio-framework's). Status: SELF (the curriculum row's own tip); not run.
Source
- MDN, Navigator: onLine property: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine · fetched 2026-10-01, saved as
sources/mdn-navigator-online.txt. "connection to LAN is considered online, even though the LAN may not have Internet access"; "you should not disable features based on the online status, only provide hints". Valid as of 2026-10-01. - MDN, Window: online event: https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event · fetched 2026-10-01, saved as
sources/mdn-online-event.txt. The event "shouldn't be used to determine the availability of a particular website". Valid as of 2026-10-01. - MDN, Page Visibility API (the curriculum row's URL): https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API · fetched 2026-10-01, saved as
sources/mdn-page-visibility.txt. Hidden tabs' timers are throttled, while "IndexedDB processes are also left unthrottled". Background for step 5; the flush onvisibilitychangeisjs-debounce-throttle's. Valid as of 2026-10-01. - Front End Interview Handbook, Front End System Design (the curriculum row's URL) and its Applications sub-page: https://www.frontendinterviewhandbook.com/front-end-system-design and https://www.frontendinterviewhandbook.com/front-end-system-design/applications · fetched 2026-10-01, saved as
sources/fih-overview.txtandsources/fih-applications.txt. The requirement questions "Does the app need to work offline?" and "Do we need to support internationalization?". As of 2026-10-01. Valid as of 2026-10-01. - web.dev, Web Vitals (the curriculum row's URL): https://web.dev/articles/vitals · fetched 2026-10-01, saved as
sources/webdev-vitals.txt. Read for context only; it keys nothing here, and the LCP and INP thresholds belong toperf-lcpandperf-inp. Valid as of 2026-10-01. - MDN, parseFloat() and SubtleCrypto: decrypt(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat and https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt · fetched 2026-10-01, saved as
sources/mdn-parsefloat.txtandsources/mdn-subtlecrypto-decrypt.txt. Valid as of 2026-10-01.
Related lessons
Checked against the tools and docs as of 2026-10-01.