Blueprint
Realtime & messaging advanced

Design Zoom

The only interview question where latency is measured in milliseconds and the media never touches your database — a media plane that's a different animal from the control plane.

asked at Zoom · Google · Microsoft Sometimes asked ~40 min walkthrough
01

Scope the requirements

Split the problem in half before you draw anything. There's a control plane (signalling, rooms, auth) that behaves like a normal web service, and a media plane (RTP, jitter buffers, SFUs) that behaves like nothing else in a system design interview. Interviewers score you on drawing that line.

Functional — what it must do

  • Create a meeting, join by link/PIN, up to 500 participants.
  • Real-time audio + video, screen share, chat sidebar.
  • Adaptive bitrate under network stress; automatic reconnection.
  • Recording to durable storage; live transcription optional.
  • Explicitly cut: virtual backgrounds, breakout-room limits, phone-in — offer them back.

Non-functional — the numbers that shape the design

  • Glass-to-glass latency under 200 ms p95 within a region.
  • 10M concurrent meetings, ~40M concurrent participants at peak.
  • Handle 30% packet loss without a call drop.
  • Regional media routing — clients never traverse an ocean for a same-city call.
  • 99.99% availability on join; call quality degrades before it drops.
Say this out loud

The two planes have completely different SLAs. I'll design the control plane for correctness and the media plane for tail latency — they scale independently and share almost no infrastructure.

02

Back-of-envelope estimates

The media plane numbers dwarf everything else. One SFU box carries ~500 participants; the whole product is capacity planning around that.

QuantityEstimateHow you got there
Concurrent participants~40M10M meetings × ~4 avg; peak business hours drive this.
Bandwidth in~40 Tbps40M × ~1 Mbps average video. Cameras off cuts this in half in practice.
SFU boxes~80,0001 SFU handles ~500 streams (100 Gbps NIC ÷ 200 kbps outbound). Spread across ~50 regions.
Signalling QPS~50k/sjoin/leave/renegotiate — small compared to media. Handled by a stateless tier.
Recording storage~50 PB/mo1% of meetings recorded; ~500 MB per hour at 1080p.
STUN/TURN traffic~5% of mediaNAT traversal fallback for restricted networks; the rest goes P2P-to-SFU.
Say this out loud

The bandwidth number is the design driver. I'm not sharding by user or by meeting size — I'm sharding by region because 200 ms is the whole latency budget.

03

Sketch the API

Control plane is REST + WebSocket for signalling. Media plane uses WebRTC — SRTP over UDP straight to the SFU.

POST/api/v1/meetings→ { meetingId, joinUrl }. Auth-required.
POST/api/v1/meetings/{id}/join→ { sfuEndpoint, iceServers, token }. Server picks the SFU by client geo.
WS/signalSDP offer/answer, ICE candidates, mute/hand-raise events.
RTPudp://sfu.region/sessMedia flows here — never touches the control plane. SRTP-encrypted.
  • Signalling and media use different transports and different servers. Signalling is TCP/WebSocket (correctness); media is UDP/SRTP (latency). Mixing them is the classic beginner mistake.
  • The join response is the pivot: the client learns which SFU to talk to based on server-side geo lookup, not client picks.
04

Data model

Control plane has small, cold data. The media plane has zero durable state — every SFU is a stateless forwarder.

meetings
id PK · owner_id · pin · created_at · ends_at?
Postgres. Read-once at join; not on the hot path afterwards.
participants
(meeting_id, user_id) PK · joined_at · left_at? · region
written once on join, once on leave — no per-frame writes.
recordings
meeting_id · s3_path · duration · started_at
S3 stores the bytes; DB stores the pointer.
sessions (in-memory)
meeting_id → { sfu_id, participants[] }
Redis; the SFU broker uses this to route new joiners to the same SFU as existing participants.
The database call

Postgres for meetings/participants — a global-ish transactional store keeps auth and join semantics simple. Redis holds the per-meeting session so the join path is one memory lookup. The SFU has no database; it holds state in RAM and dies with no data loss.

05

High-level design

Draw the two planes explicitly. Everything on the left is normal web scaling; everything on the right is UDP, jitter buffers and geographic routing.

client edge service media plane data Client WebRTC Mobile Signalling LB Anycast geo route Signalling stateless Room service Recorder SFU us-east SFU eu-west TURN relay Postgres Session S3 archives join nat sess media
left half is a normal web app; right half is UDP media. the two only meet on the join call and the recording tap.
  1. Client hits join → signalling checks auth, looks up an existing SFU for that meeting in Redis (or picks one by the joiner's geo), returns the SFU endpoint and ICE credentials.
  2. Client opens a WebRTC session directly to the SFU: SDP negotiation over the signalling socket, media flows over UDP/SRTP straight to the SFU.
  3. SFU receives one stream per publisher, forwards N-1 streams per subscriber. No transcoding, no mixing — that's the SFU model.
  4. Recording is a special subscriber: the SFU sends a copy of each stream to a recorder process that muxes to disk and uploads to S3.
  5. On network loss, the client's ICE restart runs; if the same SFU is still reachable, the session survives; if not, the room service migrates the participant to a nearby SFU.
06

Deep dives — where the interview is won

Phase 01

SFU beats mesh at 5, MCU beats nothing

Three architectures exist. Mesh: every participant sends to every other participant. Upload bandwidth is N-1 streams — great for 2-3, unusable at 5 on residential upload. MCU (mixer): one server decodes every stream and produces a single mixed output. CPU-brutal at scale, adds transcoding latency, and loses per-stream layout flexibility. SFU (forwarder): one server relays streams without decoding — client uploads once, gets N-1 down. Costs bandwidth on the server, but bandwidth is a commodity and CPU is not.

For Zoom-scale, SFU is the only realistic option; every serious modern video product uses it. The follow-up worth having ready: SFUs can still do smart things — simulcast (client sends 3 resolutions, SFU picks per subscriber) and SVC (one scalable stream, SFU drops layers) let one uplink serve heterogeneous downlinks. Say simulcast unprompted.

peers SFU A publisher B viewer C viewer SFU no decode fwd 1 up fwd
the publisher uploads once. the SFU forwards N-1 copies without decoding — this is the whole architectural bet.
Trade-offSFU can't do server-side compositing (a single mixed stream to a low-end device) — that would require MCU. Product-level fix: send simulcast layers, subscribers pick, low-end clients pull the smallest layer.
Phase 02

Geo routing that keeps latency inside the physics budget

200 ms glass-to-glass leaves ~120 ms for the network. A same-continent hop is ~40 ms; cross-Atlantic is ~100 ms already. The design has to guarantee that a meeting where everyone is in Frankfurt runs on a Frankfurt SFU — not a Virginia one just because that's where the control plane lives.

The trick: on the first join, pick the SFU nearest the joiner. Every subsequent joiner is routed to the same SFU, unless they're catastrophically far from it (say, > 150 ms), in which case a second SFU joins the mesh as a cascade — SFU-to-SFU forwarding across a fast backbone. Anycast + latency probing does the picking; the session record in Redis remembers what was picked.

peers SFUs user US-east user US-west user EU SFU us-east SFU eu-west 20ms 20ms 60ms cascade
each user talks to their nearest SFU; SFUs cascade across the backbone so no user pays the transatlantic hop twice.
Trade-offCascade adds one hop of latency for cross-region participants — worth it when the alternative is every user paying 100 ms just to reach the wrong region.
Phase 03

Losing packets gracefully — the media plane's job, not the app's

UDP drops packets. WebRTC assumes it and has three lines of defence: NACK/RTX (retransmit lost packets, useful up to ~50 ms round trips), FEC (send redundant parity so the receiver can reconstruct without a retransmit), and PLC (packet loss concealment on the decoder side, so a lost audio frame becomes a plausible-sounding sample). None of these live in your app code — they're in the WebRTC stack. Your job is to configure them and to not undo them.

Above 20% loss, no combination of these keeps the picture up. The correct product answer is to shed load: drop the camera before you drop the call, degrade the video resolution before you degrade the audio, keep chat working when nothing else does. The signalling plane sees receiver reports, the SFU sees them too, and the client picks a smaller simulcast layer or turns the camera off. Say all four moves out loud.

publisher SFU subscriber Publisher SFU NACK, FEC Sub good net Sub 20% loss 3 layers HD LD+FEC
simulcast means one uplink can serve two subscribers with wildly different downlink budgets — no transcoding on the SFU.
Trade-offFEC roughly doubles bandwidth for the audio stream — cheap for audio, prohibitive for video. Standard configuration: FEC on audio always, on video only when receiver reports show sustained loss.
07

Follow-ups you should expect

Why not just P2P for two-person calls?
Actually do — a 2-person call over an SFU is wasteful. The signalling server can flip to direct P2P (with SFU as fallback if NAT traversal fails). Above 2 or 3, the SFU wins on upload bandwidth. Adaptive: switch modes based on participant count.
How do you record 500 concurrent streams into one file?
The recorder is a subscriber that receives all N streams via the SFU, decodes and composites into a single video (grid or speaker view), and uploads chunks to S3 in real time. Composition is CPU-heavy — it's the one place transcoding happens, deliberately offline from the live path.
What breaks first at 100k participants in one meeting?
The SFU's outbound fan-out — one publisher × 99,999 subscribers doesn't fit on one NIC. The fix is a tree of SFUs: cascade through a hierarchy so no single SFU pushes more than a few thousand streams. That's how large webinars and live-streams work.
How do you handle a user behind symmetric NAT?
STUN discovers the reflexive candidate. When STUN alone can't reach the SFU (symmetric NAT with no port prediction), the ICE candidate list includes a TURN relay — media flows through TURN instead of direct-to-SFU. It costs bandwidth on your side but the call connects. Roughly 5% of clients need it.
Encryption end-to-end for the meeting?
SRTP encrypts client-to-SFU, which means the SFU can read frame headers (it must, to forward) but not the payload if you use end-to-end encryption on top. The E2E key is exchanged out-of-band via the signalling plane and never touches the SFU. It costs the ability to server-side transcribe unless the client emits transcripts.

Where candidates lose the room

  • Drawing one big media server that mixes streams — you've re-invented the MCU and lost the SFU's cost model.
  • Routing media through the control-plane load balancer — TCP-terminated media adds head-of-line blocking and multi-hop latency.
  • Ignoring geography — a Virginia-only SFU tier serving a European meeting spends 100 ms just crossing the Atlantic.
  • Forgetting that recording is a subscriber, not a special path — every SFU capability (simulcast, cascade) should apply to the recorder for free.
  • Trying to make WebRTC do retransmission over TCP because "UDP is lossy" — TCP head-of-line blocking makes real-time video worse, not better.

Concepts this leans on

Found a bug or want more?

Tell me what's broken, missing, or wrong. Every message lands in my inbox.