Lesson 7 of 8 · 58 min

FE system design II — collab editors, feeds, autocomplete

Full RADIO frontend system design for collaborative editors (CRDT/OT, undo, reconnect), infinite feeds (cursors, CLS-safe injection), and autocomplete (debounce, abort, stale races, ARIA combobox).

Lesson 7 · FE system design II

Collab editors, feeds, autocomplete

Harder prompts, same discipline

Collaborative editors, infinite feeds, and autocomplete expose data-model and race bugs that CRUD UIs hide. You still run RADIO first — then deep-dive conflict, CLS-safe injection, and stale responses.
Carry the L6 clock: 5m clarify · 5m NFRs · 15m architecture · 15m deep dives · 5m risks. These prompts punish candidates who stay on the happy path — interviewers wait for merge, CLS, and race answers.

Prompt C — Collaborative editor (Docs/Notion altitude)

Multiplayer document: concurrent editing, presence cursors, offline-ish resilience, undo/redo, rich text or blocks. FE altitude: you will not implement a full CRDT in 45 minutes — you must pick a model and explain client responsibilities.

Collab — full RADIO skeleton

text
1R — REQUIREMENTS & NFRs2  Concurrent multiplayer edits without silent loss; presence cursors;3  offline buffer + reconnect; undo of *my* ops only; large-doc virtualization;4  authz per doc; a11y: announce collab status sparingly (not every remote keystroke)56A — ARCHITECTURE7  EditorView (ProseMirror / CodeMirror / Lexical)8       ↕ binding9  SharedDoc (Y.Doc CRDT / OT doc)10       ↕11  SyncProvider (WebSocket) — auth, state vector exchange12       ↕13  Awareness channel — cursors/names (ephemeral, throttled)14  Persistence: snapshot every N ops + op log; load = snapshot + ops since15  Local UI: find bar, selection chrome; URL: doc id + heading hash1617D — DATA MODEL18  Y.Doc / OT document structure (library-owned)19  AwarenessState { userId, cursor, color, name }20  Snapshot { docId, version, bytes }2122I — INTERFACES23  WS: sync updates (binary Yjs update) + awareness messages24  HTTP: GET snapshot; POST auth token for room2526O — OBSERVABILITY & RISKS27  Metrics: sync lag, reconnect rate, doc size, undo errors28  Risks: whole-doc LWW destroy; offline fork UX; contenteditable a11y hell

CRDT vs OT (interview altitude)

  1. 01OT (Operational Transform) — transforms ops against concurrent ops; often needs a central authority ordering. ShareDB is a classic reference.
  2. 02CRDT — commutative structures; peer merge friendlier; libraries (Yjs, Automerge) common in product. Figma’s multiplayer blog is a great narrative source.
  3. 03FE job — bind editor view to shared doc model; presence channel; undo stacks that respect remote ops; reconnect resync.
Undo across users: naive local undo reverts remote work incorrectly. Proper systems undo the user's own operations in the shared model (stack of local ops inverted / Yjs UndoManager with origin tags), not “restore previous HTML.” Say this explicitly — it is a discriminator.

Collab — reconnect & offline

  1. 01On disconnect: read-only or buffer local ops in IndexedDB.
  2. 02On reconnect: exchange state vectors; receive missing ops; resolve with CRDT/OT rules.
  3. 03Show presence: “reconnecting…”; do not silently drop keystrokes.
  4. 04Large docs: lazy-load block bodies; virtualize long pages.
  5. 05A11y: contenteditable is hard — prefer well-tested engines; announce collab status sparingly.

Prompt D — Infinite feed (Twitter/Instagram-like)

Home feed: mixed media cards, cursor pagination, pull-to-refresh, new post composer, multi-column on desktop optional. SEO usually secondary (auth). CLS and media performance dominate.

Feed — full RADIO skeleton

text
1R — REQUIREMENTS & NFRs2  Infinite scroll home feed; cursor pages; pull-to-refresh; composer;3  mixed media; CLS ≤ 0.1; virtualize; “N new posts” without jump; media LCP careful45A — ARCHITECTURE6  GET /feed?cursor=&limit=20 → { items[], nextCursor }7  Prefer cursor over page=2 (stable under inserts)8  Client: infinite query cache + virtualizer + IO prefetch near end9  Optimistic prepend on create with reserved space OR “new posts” banner10  Images: width/height, blur placeholder, priority only first screen11  State: URL filters optional; server feed pages; local composer draft;12         global toast; badge counts derived from cache when possible1314D — DATA MODEL15  FeedItem { id, type, author, body, media[], createdAt, cursorKey }16  FeedPage { items, nextCursor }1718I — INTERFACES19  GET /feed?cursor&limit20  POST /posts → item (optimistic clientId → server id)21  Optional SSE: feed.invalidate or new-post notifications2223O — OBSERVABILITY & RISKS24  Metrics: CLS on feed, time-to-first-card, duplicate id rate, error retries25  Risks: offset pagination drift; auto-insert jump; missing media dimensions

Feed — CLS-safe injection & anchoring

Inserting cards above the viewport without care shifts content under the user's finger. Strategies: (1) banner “new posts” without auto-insert while reading, (2) if auto-insert, adjust scrollTop by inserted height, (3) always known media dimensions. Cursor pagination + virtualization + CLS strategy is the feed triad.

Prompt E — Autocomplete / command palette

Typeahead appears in machine coding and system design. Core: debounce input, cancel in-flight requests, ignore stale responses, keyboard navigation, ARIA combobox pattern, optional client prefix cache.

Autocomplete — full RADIO skeleton

text
1R — REQUIREMENTS & NFRs2  Suggest within ~100–200ms perceived; debounce 150–300ms; keyboard full;3  APG combobox; empty/error/min-chars; no stale overwrite; XSS-safe render45A — ARCHITECTURE6  input → debounce → AbortController fetch → rank → listbox7  Optional: client LRU prefix cache; cmd-K local fuzzy for commands8  State: local query + activeIndex; URL q optional for shareable search pages910D — DATA MODEL11  Suggestion { id, label, meta? }12  RequestSeq number for last-write-wins1314I — INTERFACES15  GET /suggest?q= → Suggestion[]1617O — OBSERVABILITY & RISKS18  Metrics: suggest latency, abort rate, stale-drop count, select rate19  Risks: race (slow “rea” overwrites “react”); missing a11y; HTML injection
ts
1// Stale-response race — the #1 autocomplete bug2let seq = 03let abortPrev: AbortController | null = null4async function onQuery(q: string) {5	const my = ++seq6	abortPrev?.abort()7	const ctrl = new AbortController()8	abortPrev = ctrl9	const res = await search(q, { signal: ctrl.signal })10	if (my !== seq) return // stale11	setItems(res)12}13// Debounce 150–300ms for typeahead; less for cmd-K local list14// Keyboard: arrow activeDescendant, Enter select, Escape close
  1. 01Debounce vs throttle — debounce for query; throttle for scroll handlers.
  2. 02AbortController — cancel superseded fetches.
  3. 03Last-write-wins by sequence number — even if abort fails.
  4. 04Empty/error/min chars — UX states interviewers watch for.
  5. 05Security — escape rendering; careful with HTML suggestions.
  6. 06A11y — role=combobox, aria-expanded, listbox options, aria-activedescendant.

Cross-prompt comparison table

text
1Concern        Chat           Feed            Collab          Autocomplete2Transport      WS+HTTP        HTTP(+WS)       WS sync         HTTP3Hard problem   scroll anchor  CLS inject      merge/undo      stale race4Virtualize     yes            yes             long docs       listbox5URL state      channel        filters         doc id          q optional6A11y focus     live msgs      new posts       caret/SR        combobox

Practice assignment shape

Run a 45-minute mock on one of: photo viewer, e-comm PDP, notifications inbox. Deliver: requirements, architecture diagram (boxes), state buckets, NFRs, two deep dives, risks. Self-score with L1 rubric.
  1. 01Photo viewer deep dives: image pyramid / tiles, memory under rapid zoom, gesture vs button affordances.
  2. 02PDP deep dives: LCP hero, variant a11y, multi-tab cart sync, revalidate on price.
  3. 03Inbox deep dives: URL selection, optimistic read, push + badge derivation, keyboard list nav.
System Design — Design Google Docs / real-time editorSystem design talk

Collab RADIO — deep dive scripts

CRDT binding narrative: Editor view emits local operations into a Y.Doc (or OT doc). Sync provider sends updates over WebSocket. Remote updates apply into the same doc; the binding repaints. Awareness channel carries cursors at higher frequency with throttle. Persistence is snapshot + ops, not “save HTML every keystroke.” UndoManager tracks local origins so Ctrl+Z does not erase a teammate’s sentence.
Offline narrative: On disconnect, buffer updates locally (IndexedDB). UI shows reconnecting; optional read-only for risky modes. On reconnect, exchange state vectors; missing ops flow both ways; CRDT converges. Surface conflicts only when domain rules require human choice (rare for plain text; more common for structured fields).

Feed RADIO — deep dive scripts

Cursor pagination narrative: Server returns opaque nextCursor tied to stable sort keys. Client stores pages in an infinite query. Offset page=2 fails when items insert above — duplicates and skips. Prefetch when the virtualizer approaches the end. Pull-to-refresh invalidates from the head cursor carefully without CLS jumps.
CLS-safe injection narrative: While the user reads mid-feed, do not silently prepend five new cards. Show “3 new posts” in a polite live region or banner; on click, insert and scroll to top, or adjust scrollTop if product requires auto-insert. Every image has width/height or aspect-ratio. First screen images may use priority; below-fold stays lazy.

Autocomplete RADIO — deep dive scripts

Race narrative: Keystrokes “r”, “re”, “rea”, “react” each spawn a request if not debounced. Even with debounce, variable RTT can return “rea” after “react”. Increment a sequence number per request; ignore responses whose seq is stale. AbortController cancels the in-flight fetch to save bandwidth. Render with APG combobox roles and keyboard.
text
1// Week-1 build slices2// Collab: read-only shared doc via Yjs demo provider → then write + awareness3// Feed: cursor list + virtualize → then composer optimistic → then new-post banner4// Autocomplete: local list filter → then remote debounce/abort → then APG keyboard56// Self-score after mock (1–5 each):7// requirements, architecture, NFRs, a11y, perf, failure modes

End-to-end collab / feed / autocomplete mock scripts

Collab 45m: clarify rich text vs plain, presence needs, offline, authz. Pick CRDT (Yjs) unless interviewer steers OT. Draw EditorView ↔ SharedDoc ↔ SyncProvider ↔ Awareness. Name undo-of-local-ops and reconnect state vectors. Deep-dive offline buffer and large-doc virtualization. Metrics: sync lag, reconnect rate. Do not invent whole-doc LWW.
Feed 45m: clarify auth, media mix, ranking opacity (client does not re-rank server feed lightly). Cursor pagination, infinite query, virtualizer, IO prefetch. CLS-safe new-post strategy (banner default). Image dimensions always. Optimistic composer with clientId reconciliation. Deep-dive injection and media LCP/CLS. Anti-pattern: offset pages.
Autocomplete 20–30m (often machine-coding adjacent): debounce, AbortController, sequence ignore, APG combobox, empty/error/min-chars, XSS-safe rendering, optional prefix LRU cache. Deep-dive the stale race with a concrete timeline (rea vs react). Close with privacy (what queries are logged) if enterprise.
  1. 01Collab senior signals — CRDT/OT choice with tradeoff; awareness separate; undo origins; reconnect.
  2. 02Feed senior signals — cursor not offset; virtualize; CLS injection strategy; media dimensions.
  3. 03Autocomplete senior signals — debounce ≠ race-safe; abort+seq; combobox APG; security of HTML.
  4. 04Shared — state buckets map; NFRs unprompted; week-1 slice; risks last five minutes.
text
1// Feed insert without jump (banner path)2// 1. SSE/push: newPostsCount++3// 2. Show button: `${n} new posts` (aria-live polite once)4// 3. onClick: fetch head page; prepend; scrollTo(0) intentionally5// Auto-insert path only if product requires; then scrollTop += insertedHeight
Practice cadence: two 45-minute mocks per week until interviews — rotate collab, feed, autocomplete, and one wildcard (PDP/inbox/photo). After each mock, write five bullets: what you clarified late, which NFR you forgot, which deep dive was weak, what metric you named, what you would build week one. Weak deep dives become the next day’s whiteboard-only drill without coding.
docsWAI-ARIA — Combobox patternW3CdocsMDN — AbortControllerMDNdocsYjs documentation (CRDT collab)YjsarticleFigma — How Figma's multiplayer technology worksFigma EngineeringarticleGreatFrontEnd — News FeedGreatFrontEnddocsTanStack VirtualTanStack

Checkpoint

Why is last-write-wins on an entire collab document a bad merge strategy?

AIt is slow on mobile networks.BConcurrent edits get destroyed — one side silently wins instead of merging operations/structures.CIt requires WebSockets.
Sign up free to answer and see why

Checkpoint

Infinite feed: user is reading mid-list; you auto-insert 5 new posts at the top without adjusting scroll. What fails?

ACORS.BScroll position / CLS — content shifts under the reader.CServer cursor pagination only.
Sign up free to answer and see why

Checkpoint

Autocomplete: slow response for “rea” arrives after fast response for “react”. Risk?

AUI shows “rea” results last and overwrites better “react” results (stale race).BTLS fails open.CDebounce becomes negative.
Sign up free to answer and see why

Checkpoint

Best pagination style for a social feed with frequent inserts?

Apage=1,2,3 offsets.BCursor/token based on stable sort keys from the server.CDownload the entire feed once.
Sign up free to answer and see why

Checkpoint

What is the FE's job in a Yjs-style collab stack?

AImplement the full CRDT math from scratch in the interview.BBind the editor to the shared doc, sync provider, awareness/presence, undo semantics, and reconnect UX.COnly style the cursors in CSS.
Sign up free to answer and see why

Can you deep-dive collab merge, feed CLS injection, and autocomplete races in one mock?

New to itGetting thereConfident

Takeaways

  • Collab: CRDT/OT choice, presence, undo-of-local-ops, reconnect resync — full RADIO.
  • Feed: cursor pages, virtualization, CLS-safe injection, media dimensions.
  • Autocomplete: debounce + AbortController + sequence ignore + combobox a11y.
  • Whole-doc LWW and offset pagination are common senior traps.
  • Practice a third prompt end-to-end with the L1 rubric.

Next: AI-in-the-UI, machine-coding patterns, and the capstone mock.

Sources

Free to read · better with Enzo

Learn it with Enzo

Save your progress, answer the checkpoints, and let Enzo quiz you on what you just read.