High-Level Design and the API
In one line: the bytes go straight to blob storage, the control path goes through your servers, and the two are separated on purpose.
The pipeline
Seven steps, and the interesting design decisions are all in the first three.
Why the bytes don't go through your servers
The naive version routes the upload through the application tier: client posts a file to a server, the server writes it to storage. That is wrong at this scale, for a simple reason.
A video can be tens of gigabytes. Routing it through a service whose actual job is authorization makes every API server a bandwidth bottleneck, and forces you to scale that tier on upload volume rather than on request volume. Those are different numbers by orders of magnitude.
So the client asks the upload service for a presigned URL — a time-limited, single-object credential to write directly to blob storage. The service authenticates the user, checks quota, records metadata, and hands back the URL. The bytes never touch it.
Multipart, and why resumability is a requirement
The client does not send one enormous PUT. It splits the file into parts of roughly 5–10 MB and uploads them independently.
That buys three things:
- Parallelism. Several parts in flight at once saturates the connection.
- Cheap retry. A failed part is a few megabytes to redo, not the whole file.
- Resumability. This is the one that matters.
The requirement is uploads of tens of gigabytes, often from phones on mobile networks. A design where a dropped connection restarts a 10 GB upload does not work. So the server tracks per-part state, and a resume is just a query.
The client computes a fingerprint per part before uploading; the blob store returns an ETag per part after. Recording both means the server can verify it is assembling the file the client actually sent, rather than trusting a claim about it.
Completion is what enqueues the transcoding job. Until then, the object is an incomplete upload, not a video.
Why not upload straight to the encoder?
If the bytes already bypass the application servers, why keep a service in the control path at all?
Three reasons, and none of them are about speed.
The encoder must not be internet-facing. Transcoding is CPU-intensive and parses untrusted binary data — historically a rich source of vulnerabilities in media libraries. Exposing it directly means an attacker can feed malformed video into your most computationally expensive, most parser-heavy service.
Abuse control needs a gatekeeper. Rate limiting, authentication, and quota enforcement live at the upload service. Without it, one client can saturate the encoder fleet.
Deduplication has to happen before transcoding, because transcoding is the expensive step. Catching a duplicate after paying to encode it into five renditions wastes the compute as well as the storage.
And the objection about added delay does not apply: the upload path is allowed to be slow, because nobody is waiting to watch the video they just uploaded.
The API
Grouping by path rather than by service is the useful view, because the two have opposite requirements and scale independently.
Upload
POST /uploads (title, description, category_id, tags,
default_language, privacy_settings,
size_bytes, part_fingerprints[])
-> uploadId, presigned URLs, per-part status
PATCH /uploads/{id}/parts (part_number, etag)
GET /uploads/{id} -> per-part status (the resume)
POST /uploads/{id}/complete -> assemble, enqueue transcode
Metadata is submitted up front, alongside the request for a URL, so the video record exists before the bytes do. That is what gives the client something to poll and the pipeline something to attach renditions to.
Watch
GET /videos/{id} -> metadata, manifest URL, nearest edge
GET <manifest URL> -> segments x qualities x codecs
GET <segment URL> -> one segment at one quality
Three requests, and the split is deliberate. Metadata is small and cacheable per video. The manifest is small and changes only when renditions change. Segments are large, numerous, and served from the edge.
Interaction
POST /videos/{id}/reaction (reaction) -- "like" | "dislike" | null
POST /videos/{id}/comments (comment_text)
GET /search (q, length, quality, upload_date)
One endpoint sets the reaction rather than three endpoints for like, dislike, and remove. These are not three independent actions — they are transitions of one piece of state, this user's reaction to this video. An endpoint that sets the value is naturally idempotent: calling it twice with the same value leaves the same state.
Note what the API hides. Those reaction counters are sharded counters underneath, because a viral video takes millions of concurrent increments. The client has no reason to know, and increments are fungible.
Key takeaway
The bytes go direct to blob storage through a presigned URL while the control path keeps authentication, quota, and dedupe — the trust boundary without the bandwidth. Uploads are multipart so a dropped connection resumes by asking which parts landed rather than restarting tens of gigabytes. The encoder stays off the public internet because it is CPU-heavy and parses untrusted media. And playback is three requests, because the manifest is what lets the client choose a quality without server coordination.
Interview signal by level
| Level | What a strong answer sounds like |
|---|---|
| L4 | "Upload goes to a server, gets encoded, stored, and served through a CDN." |
| L5 | Names the upload mechanism: "the client gets a presigned URL and uploads directly to blob storage in multipart chunks, so the bytes don't traverse the API tier and a failed part can be retried on its own." |
| Staff+ | Separates control path from data path and makes resumability a requirement: "the bytes bypass the servers but the control path keeps auth, quota and dedupe — and dedupe has to be there because it must happen before we pay to transcode. Uploads are tens of gigabytes from phones, so a resume is a query for which parts landed, verified by part ETags. The encoder itself is never internet-facing: it's the most parser-heavy service we own." |
Next: the schema and the components underneath.