System Design · Real-Time System Design
WebRTC vs. Traditional Socket-Based Communication
Sockets always relay through a server; WebRTC tries to skip the server entirely. Here's what that trade actually costs you.
Open a WebSocket and you know exactly what you get: a persistent, full-duplex pipe between a client and a server. Every message either side sends travels through that one connection, and the server sees all of it. WebRTC starts from a different premise entirely — it was built to let two browsers talk directly to each other, without a server relaying every byte in between. That single design choice is the root of almost every practical difference between the two, from how you set up a connection to how you should decide which one to reach for.
Client-server vs. peer-to-peer
A plain TCP socket or a WebSocket is inherently a client-server arrangement. If Alice wants to send Bob a chat message, that message goes Alice → server → Bob. The server is always in the data path — it can log the message, fan it out to other clients, apply back-pressure, and enforce whatever business rules it wants, because nothing reaches Bob without passing through it first.
WebRTC flips that around. Once a connection is established, media and data flow directly between the two peers whenever the network allows it — the application server that helped set things up is no longer in the path at all. This is what makes WebRTC attractive for video calls and low-latency data channels: the bytes take the shortest possible route between the two participants instead of detouring through infrastructure you have to pay for and scale.
Signaling: the part WebRTC deliberately leaves out
With a WebSocket, connecting is trivial — the client opens a connection to a known URL and the handshake is done. WebRTC has no equivalent built-in mechanism, and that's not an oversight; it's intentional. Before two peers can exchange media or data, they need to agree on codecs and session parameters (the SDP offer/answer) and discover the network paths that might actually reach each other (ICE candidates). WebRTC defines the format of that information but says nothing about how it gets from peer A to peer B — that's left entirely to you.
In practice, that means every WebRTC application ships its own signaling channel, almost always a WebSocket or an HTTP API talking to a server you control. The irony is worth sitting with: you can't build a peer-to-peer WebRTC connection without first standing up a client-server one to bootstrap it.
// Signaling happens over a channel YOU build — usually a WebSocket.
// WebRTC has no opinion on how offers/answers/candidates get there.
socket.on("offer", async ({ sdp, from }) => {
await pc.setRemoteDescription(sdp);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit("answer", { sdp: answer, to: from });
});
pc.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", { candidate: event.candidate, to: from });
}
};
// Once negotiation completes, media/data flows peer-to-peer —
// the signaling socket is no longer in the path.Signaling never fully goes away
Even after the peer-to-peer connection is up, you still need signaling for anything that changes session state later — renegotiating when someone toggles their camera, adding a screen share track, or tearing the call down cleanly. The signaling socket usually stays open for the life of the session, just no longer carrying the media itself.
Media is the point, not an afterthought
A WebSocket gives you a byte pipe and nothing else — what you send is entirely up to you. WebRTC, by contrast, was designed around real-time media from the start, and it shows in everything it bundles for free:
- Built-in audio/video codecs — Opus for audio, VP8/VP9/H.264/AV1 for video — negotiated automatically during the offer/answer exchange.
- Adaptive bitrate — WebRTC continuously measures packet loss and round-trip time and adjusts encoding bitrate on the fly, so a call degrades gracefully on a bad connection instead of falling over.
- Jitter buffers — packets rarely arrive at a perfectly even cadence; a jitter buffer smooths out that variance before handing audio/video to playback, trading a little latency for a lot less choppiness.
- Echo cancellation and noise suppression — handled at the media engine level, so a laptop mic and speaker don't howl at each other.
None of this comes with a plain socket. If you tried to stream video over a WebSocket, you'd be responsible for encoding, packetizing, reordering, jitter smoothing, and loss recovery yourself — WebRTC gives you a media pipeline that already solved those problems, tuned over more than a decade of production use in Chrome and beyond.
Latency: the payoff for the extra complexity
This is where the peer-to-peer design earns its keep. Every hop a packet takes adds latency, and relaying through an application server — Alice → server → Bob — adds a round trip that a direct Alice → Bob path skips entirely. For a chat app, that extra hop is imperceptible. For a video call or a competitive multiplayer game, it's the difference between feeling instantaneous and feeling laggy.
That advantage isn't guaranteed, though. Most real devices sit behind NATs and firewalls that a direct connection can't always punch through, and when it can't, WebRTC falls back to relaying traffic through a TURN server — at which point you're paying the same extra hop a WebSocket would have cost you, plus the overhead of having negotiated for a direct path first. How often that fallback kicks in, and what it takes to minimize it, is really its own topic — one worth a dedicated look at NAT traversal and ICE in a follow-up article.
When a plain socket is the right call
None of this makes WebRTC strictly "better." For a large share of real-time features, a WebSocket is not just adequate — it's the better engineering choice. Chat messages, live notifications, collaborative cursors, stock tickers, and dashboard updates are all small, infrequent, non-media payloads. They don't need adaptive bitrate or jitter buffers, and they benefit enormously from having a server in the loop: one place to authenticate, persist, rate-limit, and fan out to many recipients at once.
Ask what the server would be for
If your server needs to see every message anyway — to store it, to broadcast it to a room, to enforce permissions — a WebSocket is simpler and gets you there faster. Reach for WebRTC only when skipping the server is actually the goal: low-latency media, or data that genuinely doesn't need a middleman.
Plenty of production systems use both: a WebSocket for signaling, presence, and chat, and a WebRTC data channel or media stream layered on top only for the pieces that need peer-to-peer speed. The two aren't really competitors so much as tools with very different defaults — one optimizes for a server that's always watching, the other for a server that gets out of the way as soon as it can.
Key takeaways
- WebSockets always relay through a server; WebRTC tries to establish a direct peer-to-peer path and only falls back to a relay when NAT traversal forces it.
- WebRTC has no built-in way to find the other peer — you must build your own signaling channel (usually WebSockets or HTTP) to exchange SDP offers/answers and ICE candidates before the connection exists.
- WebRTC bundles codecs, adaptive bitrate, jitter buffers, and echo cancellation for free; a plain socket gives you none of that.
- A direct peer-to-peer path can meaningfully cut latency versus a server-relayed one, which matters most for audio/video and other real-time media.
- For small, infrequent, non-media messages — chat, notifications, dashboards — a plain socket's operational simplicity usually beats WebRTC's added complexity.
