WebRTC in 15 Minutes (+ How Zoom Scales)
“Peer-to-Peer, No Servers” Is a Beautiful Lie
You've probably heard WebRTC described in five magic words: peer-to-peer, no servers. Your browser talks directly to my browser, video flows between us, and no company sits in the middle. It's the pitch behind every "serverless" video-call demo, and it is — I'll be honest up front — wrong twice over. Untangling exactly how it's wrong is this entire lesson, and it's one of the most clarifying things you can learn about how the real-time internet actually works.
First, what WebRTC even is. Everything in §7 so far moved text: JSON over WebSockets, tokens over SSE, a POST for a webhook. WebRTC moves something far heavier and far fussier — live audio and video, between browsers. That's a different universe of hard: enormous bitrate, brutal latency requirements (a video frame that's 300ms late is garbage, throw it away), lossy networks, codecs, encryption, echo cancellation. WebRTC is the browser's built-in stack for all of it — the thing that lets a plain web page place a video call with no plugin. That part is genuinely amazing, and genuinely peer-to-peer for the media of a 1:1 call.
But here's the double lie. Lie one: even a "direct" 1:1 call can't start without servers. Two browsers sitting behind home routers literally cannot find or reach each other on their own — they need a matchmaker to introduce them, a mirror to discover their own addresses, and sometimes a relay to carry the traffic. Lie two: the instant you go from two people to a group, peer-to-peer collapses under its own arithmetic — and the fix is, inevitably, a server in the middle. "How Zoom scales" is the answer to that second lie, and it's the payoff of the lesson. Let's take the lies in order.

Lie One, Part A: Getting Two Browsers to Agree
Before any video can flow, the two browsers have to negotiate what they're even going to send. Does the other side speak the VP9 codec or only VP8? What resolutions, what frame rates, what audio format, which ports? Neither browser can guess; they have to tell each other. This exchange is called signaling, and it produces a document called an SDP (Session Description Protocol) blob — think of it as a contract: "here's everything I can do."
The flow is an offer/answer:
- The caller's browser generates an SDP offer: "I support these codecs, resolutions, and settings."
- That offer has to get to the callee somehow — and here's the surprise: WebRTC does not tell you how. It deliberately leaves signaling out of the spec. You build the channel that carries it, and in practice it's almost always a WebSocket (yes — the one from two lessons ago, doing exactly its job: a live two-way pipe to shuttle setup messages) or a plain HTTP endpoint.
- The callee sets the offer as its "remote description," generates an SDP answer — its compatible subset — and sends it back over that same channel.
So the very first thing a "serverless" peer-to-peer call needs is… a server: a signaling server you run, standing between the two peers, relaying their offer and answer. It doesn't touch a single frame of video — but without it, the two browsers never even learn the other exists. WebRTC standardized the hard media part and, on purpose, handed you the introductions.
Lie One, Part B: Getting Two Browsers to Find Each Other
Agreeing on codecs is only half of setup. The other half is a genuinely hard networking problem: the two browsers don't have addresses you can dial. Your laptop's IP is something like 192.168.1.14 — a private address behind your home router, which does NAT (Network Address Translation) to share one public IP across all your devices. My browser can't send packets to 192.168.1.14; that means nothing on the public internet. Both peers are hidden behind routers, each shouting into the void, neither reachable. This is the reason "just connect the two browsers" is naïve.
WebRTC solves it with a trio of helpers, and understanding the split is where people go from confused to clear:
- STUN (Session Traversal Utilities for NAT) is a mirror. Your browser asks a tiny public STUN server, "what do I look like from out there?" and the server replies with your public ip:port as seen from the internet. Now your browser knows its own reachable address (a "server-reflexive candidate") and can share it via signaling. STUN is cheap and stateless — no media ever flows through it, it just answers "here's how you appear."
- ICE (Interactive Connectivity Establishment) is the negotiator. Each peer gathers every address it might be reachable at — its local one, its STUN-discovered public one, a relayed one — and ICE systematically tries every pairing, "hole-punching" through the NATs, until it finds a path where packets actually get through. When it does, media flows directly, peer-to-peer — the promise, delivered.
- TURN (Traversal Using Relays around NAT) is the fallback relay. For roughly 10–20% of networks — strict corporate firewalls, symmetric NATs — no direct path exists, no matter how clever ICE is. Rather than fail the call, WebRTC routes the media through a TURN server that relays it between the peers. It works, but notice what just happened: the media is now flowing through a server, using real bandwidth you pay for. This is no longer peer-to-peer — and every production system provisions TURN anyway, because "the call works for 85% of users" is not a product.
Then a DTLS-SRTP handshake encrypts everything (WebRTC media is always encrypted — mandatory), and it all rides UDP, because for live media a dropped frame is better than a stalled one. So there's lie one, fully exposed: a "peer-to-peer" 1:1 call quietly leans on a signaling server to introduce the peers, a STUN server to discover their addresses, and — for a sixth of the internet — a TURN server to carry the whole conversation.

Lie Two: The Group-Call Explosion
Now the second, bigger lie — and it's pure arithmetic, so let's just do the arithmetic. Suppose a 1:1 call works beautifully, direct peer-to-peer. Add a third person, then a fourth. The naïve extension is full mesh: everybody connects directly to everybody else. It's still "serverless," still pure P2P — so what could go wrong?
This: in a mesh of N people, your browser must send your camera's video to every other person separately — there's no server to copy it for you — so you upload your stream N−1 times. And it's not just you; everyone does, so the total number of peer connections is N(N−1)/2, which grows like N². Put real numbers on your upload, at a modest 2.5 Mbps per 720p stream against a typical ~10 Mbps home uplink (I ran these):
| People | Your upload (mesh) | Connections | Your uplink survives? |
|---|---|---|---|
| 2 | 2.5 Mbps | 1 | yes |
| 4 | 7.5 Mbps | 6 | yes |
| 5 | 10 Mbps | 10 | right at the edge |
| 6 | 12.5 Mbps | 15 | no — the call breaks |
| 12 | 27.5 Mbps | 66 | no |
| 50 | 122.5 Mbps | 1,225 | absurd |
Read the table and feel the wall. Mesh is wonderful for 2–4 people and dead by about 5, because your home connection simply cannot upload five, ten, fifty copies of your face at once. This is the same N² combinatorial blow-up you've met elsewhere in the course, wearing a webcam. And there is no clever tweak that fixes mesh — the problem is structural: each peer is doing the fan-out that a server should do. Which points at the only real answer: put a server in the middle. The rest of the lesson is the two shapes that server can take.
The Server in the Middle: SFU
Here's the elegant fix, and the one behind almost every video call you've ever been on. Instead of uploading your stream N−1 times, you upload it exactly once — to a server — and the server does the fan-out, forwarding your stream to everyone else. That server is an SFU: a Selective Forwarding Unit.
Sit with what "forwarding" means, because the name is precise and people get it wrong. An SFU routes streams; it does not touch the pixels. Your video arrives, and the SFU forwards that exact encoded stream to the other participants — untouched, unmixed. Each participant still receives N−1 separate streams (one per other person) and lays them out into a grid on their own screen. What changed is only the upload: it drops from N−1 all the way to 1, forever, no matter how big the call gets. Your uplink is now a flat 2.5 Mbps whether it's a 5-person standup or a 100-person all-hands. That single change is what makes group video possible on a home connection.
The cost didn't vanish — it moved to the server, which now ingests one stream per person and egresses roughly N(N−1) streams (it forwards everyone to everyone). But that's bandwidth, which servers have in abundance and you don't, and crucially the SFU does no per-frame CPU work — it's a smart router, not a video editor. It's also cheap enough to be the default: public figures put it around a few hundred dollars a month for 100 concurrent participants. And because the SFU only forwards the (encrypted) streams and never decodes them, it can even preserve end-to-end encryption. This is the answer to "how does Zoom scale?": Zoom, Google Meet, and Microsoft Teams all run SFU-style media routers. When you're in a big meeting, your laptop is uploading one stream to a forwarding server, and that server is the reason the call doesn't melt your Wi-Fi.
When One Stream Is Better: MCU
There's a third shape, and it's the right answer often enough to know cold. What if even downloading N−1 streams is too much — a cheap phone on a weak network, or a device that can only handle one incoming video? Then you want the server to do more than forward: you want it to mix. That server is an MCU: a Multipoint Control Unit.
An MCU takes all N incoming streams, decodes them, composes them into a single combined video (the classic grid, built server-side), re-encodes that composite, and sends each participant one stream — the finished picture. Now every client uploads 1 and downloads 1, and the client can be almost anything: an old phone, a landline audio bridge, a set-top box, a browser that just wants a single video element. For weak clients, legacy interop, and "record the whole meeting as one file" or "broadcast one clean feed," the MCU is genuinely the better tool.
But look at what the server now does per frame: decode N streams, lay them out, encode the result — over and over, in real time. That's heavy CPU/GPU work, and it's expensive — commonly 10–50× the cost of an SFU for the same number of participants. It also adds latency (all that decode/compose/encode takes time), throws away per-stream flexibility (everyone gets the same server-chosen layout, so you can't pin or reorder tiles client-side), and breaks end-to-end encryption — the server must be able to see the actual pixels to mix them, so the media can't stay encrypted end to end. The MCU scales the client down to one stream by scaling the server's cost way up. It's a deliberate trade, not a free win — which is exactly the kind of thing you should now go feel on a live graph.
See It: Scale the Call
The whole lesson comes down to one graph you can drag, so drag it. Below is a live video call: participants in a ring, streams drawn as moving dots on the arrows between them, and the meters that actually decide the architecture — your upload, your download, the server's bandwidth, the server's CPU.
Start in mesh and add people. Watch the graph tangle into a hairball as every new face wires to every existing one, and — this is the point — watch your own upload meter climb by a whole video stream per person until it crosses your home-uplink ceiling, turns red, and the call freezes. You'll hit the wall around five people, exactly as the table promised. For the full absurdity, type 50 into the box: 122 Mbps of upload across more than a thousand connections.
Now flip to SFU and feel the relief: the hairball snaps into a clean star around a server, and your upload drops to a single stream and stays there no matter how many people you add — the cost slides over to the server's bandwidth meter. That's the Zoom move, live. Flip once more to MCU and your download collapses to one mixed stream too (a weak phone would thank you) — but now the server's CPU meter redlines, because it's decoding and re-encoding every frame. Read the verdict line as you go: it tells you which topology a real product would actually choose for the group size you've dialed in.

So How Does Zoom Actually Scale?
Put it all together into the decision you'd defend in a design review. The question is never "WebRTC or not" — it's which topology, and it's decided by group size and client power:
- 1:1, or 2–4 people: pure mesh / P2P. Zero media-server cost, lowest latency, best quality, and the uplink math still works. This is the one case where "peer-to-peer, no servers" is almost true (you still need signaling + STUN, and TURN for the unlucky 15%).
- 5 to 100+ people: an SFU. This is the workhorse, the default, the answer to the N² wall — each person uploads once, the server forwards, quality and per-tile layout are preserved, and E2EE survives. It is what Zoom, Meet, and Teams run, and it's what you should reach for unless you have a specific reason not to.
- Weak/legacy clients, recording, or one-feed broadcast: an MCU. When the client can only handle a single stream, or you need one composited output to record or broadcast, pay the server-side CPU and mix. Often systems run a hybrid: an SFU for the interactive participants, with an MCU-style mixer bolted on just to produce the recording or the live broadcast feed.
And the honest footnote on "how Zoom scales" beyond the topology: it's SFU-style forwarding plus tricks like simulcast (each sender uploads a couple of quality levels, and the SFU forwards the right one to each viewer based on their screen and bandwidth) and geographically distributed media servers (§5's "put the server near the user," now carrying video). But the load-bearing idea, the one that turns an impossible N² mesh into a working 100-person meeting, is the simplest one in the lesson: stop making every peer do the fan-out, and give that job to a server.
Mental-Model Corrections
- "WebRTC is peer-to-peer, so there are no servers." Only the media of a 1:1 call is P2P — and only after a signaling server introduced the peers and STUN discovered their addresses. ~15% of networks need a TURN relay carrying the media, and any group call needs an SFU/MCU. "Serverless" is marketing.
- "STUN and TURN are basically the same." STUN is a mirror — it tells you your public address and no media flows through it (cheap). TURN is a relay — your actual media flows through it when a direct path is impossible (expensive). Discover vs. carry.
- "Signaling is part of WebRTC." No — WebRTC deliberately leaves signaling to you (usually a WebSocket). It standardizes the media path, not how the peers first find each other.
- "For a group, just connect everyone to everyone." That's full mesh, and it's N²: every added person makes everyone upload another stream, so home uplinks die around five people. Groups need a server in the middle.
- "An SFU mixes the videos." No — an SFU forwards each stream untouched; the client receives N−1 streams and lays them out. The MCU is the one that decodes and mixes into a single composite.
- "The MCU is the scalable option because clients get one stream." It scales the client, not the system — the server now does N decodes + a compose + N encodes per frame (10–50× an SFU) and can't do E2EE. You moved the cost, and grew it.
- "WebRTC runs on TCP for reliability." UDP-first — for live media a late frame is useless, so you drop it. Reliability would stall the stream; latency wins.
Try It Yourself: Watch the Explosion, Then Watch the Real Thing
Two ways to make this concrete — one you can run, one you can spy on.
1) The mesh explosion, by the numbers. A dozen lines of Node print the wall you just dragged into. Save as webrtc_bw.js:
// webrtc_bw.js — the group-call bandwidth explosion, by the numbers. No dependencies.
const BITRATE = 2.5; // Mbps per 720p video stream
const UPLINK = 10; // a typical home upload ceiling, Mbps
const meshUp = (n) => (n - 1) * BITRATE; // mesh: you upload your stream to everyone
const sfuUp = () => 1 * BITRATE; // SFU: you upload once, to the server
const conns = (n) => n * (n - 1) / 2; // mesh: every pair connected
console.log('N mesh-up SFU-up conns over-uplink?');
for (const n of [2, 4, 5, 6, 12, 50]) {
const mu = meshUp(n);
console.log(String(n).padEnd(4), (mu + ' Mbps').padEnd(10),
(sfuUp() + ' Mbps').padEnd(9), String(conns(n)).padEnd(7),
mu > UPLINK ? 'YES — mesh breaks' : 'ok');
}
Run node webrtc_bw.js. You should see mesh survive to 5 people and shatter at 6, while the SFU column never budges from 2.5:
N mesh-up SFU-up conns over-uplink?
2 2.5 Mbps 2.5 Mbps 1 ok
4 7.5 Mbps 2.5 Mbps 6 ok
5 10 Mbps 2.5 Mbps 10 ok
6 12.5 Mbps 2.5 Mbps 15 YES — mesh breaks
12 27.5 Mbps 2.5 Mbps 66 YES — mesh breaks
50 122.5 Mbps 2.5 Mbps 1225 YES — mesh breaks
That single flat 2.5 Mbps SFU column is the entire reason group video calls exist.
2) Spy on a real call. In Chrome, open a Google Meet (or any WebRTC app) in one tab, then open a second tab to chrome://webrtc-internals. You're now looking at the live guts of the call: expand the peer connection and you'll see the actual SDP offer/answer, the ICE candidate pairs it tried (host, srflx via STUN, maybe relay via TURN), and the RTP stats. Watch the outbound streams: on a multi-person Meet you'll see your browser sending essentially one video up (to Google's SFU) while receiving several down — the exact architecture from the graph above, running in front of you. Toggle your camera and watch the outbound bitrate move in real time.
Recap & What's Next
- "Peer-to-peer, no servers" is wrong twice. WebRTC is the browser's stack for live audio/video, and it's truly P2P only for the media of a 1:1 call.
- Even that call needs servers to start: a signaling server (you build it, usually a WebSocket) swaps the SDP offer/answer; STUN mirrors back each peer's public address to defeat NAT; ICE finds a working path; and TURN relays the media for the ~15% of networks where no direct path exists (no longer P2P). Then DTLS-SRTP encrypts it, over UDP.
- Groups break pure P2P by arithmetic: in a mesh of N, each peer uploads N−1 streams and there are N(N−1)/2 connections — your home uplink dies around 5 people. The fix is a server in the middle.
- SFU (Selective Forwarding Unit): upload once, the server forwards to everyone; your uplink stays flat, cost moves to server bandwidth, E2EE survives. This is how Zoom/Meet/Teams scale — the default.
- MCU (Multipoint Control Unit): the server mixes all streams into one; clients upload 1 and download 1 (great for weak/legacy clients, recording, broadcast) — but pay 10–50× server CPU and lose E2EE and layout flexibility.
- Decide by size: 2–4 → mesh; 5–100+ → SFU; single-stream needs → MCU (often a hybrid).
That "put a server in the middle so producers and consumers don't each do the fan-out" instinct is bigger than video. It's the shape of the entire back half of this section. So far, though, every conversation — even a webhook — has been synchronous in spirit: something happens, and a response or a stream flows right now. The next lesson steps back and asks the question that reorganizes large systems: when a thing happens, do you wait for it to finish, or do you hand it off and trust it'll get done? That's Sync vs Async: The Decoupling Decision — and it's where queues, the servers-in-the-middle of the async world, begin.