</> MAANG.io
system design · 301

Advanced

Master system design interviews with scalable architecture patterns, distributed systems, and real-world design challenges.

0/480 solved 0% complete

Section 4: The Contract Writers Type Into - API Design (with pushback)

Executive Summary: Section 3 found two workloads with almost nothing in common: an editor that autosaves about 450 times per publish, and a read path the CDN nearly owns. We lay out the whole surface, walk Maya's autosave and publish and Daniel's read, then defend five shapes under pushback: full-document saves, publish as an action, a separate /me, the ID in the URL, and deduplicated claps.

"Show me the API." Put the whole surface on the board first, one line per endpoint, before arguing about any of it.

Job Endpoint
Start a post POST /v1/publications/{pubId}/posts → 201 {postId, draftVersion: 0}
Load draft GET /v1/posts/{postId}/draft → {doc, draftVersion, updatedAt, deviceLabel} + ETag
Autosave PUT /v1/posts/{postId}/draft + If-Match → 200 {draftVersion} or 412
Save a copy POST /v1/posts/{postId}/revisions {doc, clientSaveId, reason: "conflict"} → 201 {revisionId}
Publish, schedule POST /v1/posts/{postId}/publish {draftVersion, publishAt?} → 200
Unpublish POST /v1/posts/{postId}/unpublish → 200
History, restore GET /v1/posts/{postId}/revisions, POST /v1/posts/{postId}/revisions/{revisionId}/restore
Images POST /v1/uploads → {uploadUrl, imageId}
Read an article GET /@{handle}/{slug}-{postId} → HTML
Personal state GET /v1/posts/{postId}/me → {claps, following}
View beacon POST /v1/events/views (sendBeacon) → 204
Clap POST /v1/posts/{postId}/claps {count, clientEventId} → {yourClaps}
Follow, unfollow PUT /v1/me/follows/{pubId}, DELETE /v1/me/follows/{pubId}
Lists GET /v1/me/feed?cursor=, GET /v1/publications/{pubId}/posts?cursor=
Search (P1) GET /v1/search?q=&cursor=

"Three rules hold everywhere. No body carries an authorId or userId: who you are comes from the session, and every draft call checks that you're a member of the post's publication. {postId} is always the random 12-hex public ID; internal tables, KV keys and Kafka use a 64-bit time-ordered post_id, translated once on a unique index. And the API splits into two halves with opposite caching rules: article HTML is public and cached at the edge, while everything under /me and the editor is Cache-Control: private, no-store and never touches the CDN."

Autosave: The Endpoint That Takes the Load

Maya's editor sends the whole document 2 s after typing pauses, and at least every 10 s during continuous typing (a debounce with a 10 s max-wait), averaging about 3 a minute:

PUT /v1/posts/a81f3c9e2b7d/draft
If-Match: "314"
{ "doc": { "type": "doc", "content": [ … ] }, "clientSaveId": "c7e1…" }

200 OK
{ "draftVersion": 315 }

If-Match carries the version this device last saw, and the editor service turns it into one conditional write: write only if the stored version is still 314. That is the whole concurrency model, at p95 ≤ 300 ms and about 1,560 saves/s at peak.

The interesting case is the second device. Maya's phone went offline on the streetcar holding version 311, and her laptop has since moved the draft to 315. When the phone reconnects:

Sequence: the laptop's PUT with If-Match 314 succeeds at version 315, then the phone's stale PUT with If-Match 311 gets a 412 with currentVersion 315, the phone saves its copy as a checkpoint revision, fetches the laptop version, and Maya picks

The 412 Precondition Failed body carries the current version and its metadata, {currentVersion: 315, updatedAt, deviceLabel: "MacBook, 9:42 p.m.", lastSaveId}, so the phone can say edited on your MacBook instead of a bare error. The phone files its unsynced copy with POST …/revisions, fetches the laptop's version with GET …/draft, and Maya picks. Keeping hers is a PUT with If-Match: "315". Nothing is overwritten silently; Section 7's autosave dive covers how lastSaveId decides when the dialog appears.

clientSaveId handles a smaller problem. If the laptop's save of 315 commits but the response is lost, the retry still carries If-Match: "314" and would get a 412 against its own write. The server sees the same clientSaveId on the save that produced 315 and returns 200 {draftVersion: 315} instead.

Quick check before moving on:

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.