deep dive 1 The realtime channel
'Online' here means one narrow thing: your own phone can be reached by a server, right now. (Seeing which of your friends are online is a different feature, and we leave it for the end.) So the question is: what kind of channel lets a server put a message on your phone's screen the moment that message exists, without the phone asking for it?
There are three honest ways to get a message onto a phone: keep asking, ask and wait, or hold a line open.
Keep asking is what the day-one version already does — polling, just turned up. It has a wall built into it. To make delivery feel instant, the phone would have to ask many times a second, and almost every answer would still be 'nothing new.' So the request rate explodes while the useful work stays the same. And even then, there is always a gap between polls. You can pay more and more and never actually reach 'instant.'
Ask and wait is smarter. The phone sends a request, and the server simply does not answer yet. It holds the request open until a message actually exists, then replies with it. This is called long-polling, and it genuinely delivers promptly — real chat products shipped on it for years. But look at what it does: it holds a connection open for the wait, then tears it down after every single message and immediately opens a new one. At millions of messages a second, that is millions of teardowns and re-opens a second, for connections we wanted open the whole time.
Both dead ends point at the same fact. If a server must be able to reach a phone at any moment, something has to stay open between them the whole time. Once you accept that, the sensible move is to hold that connection once, openly, and stop paying to recreate it.
Persistent WebSocket. Every online phone opens one WebSocket and keeps it open. A WebSocket starts life as an ordinary web request carrying an Upgrade header. After the server accepts it, the connection does not close — it stays open and becomes two-way, which means the server can send data down it at any time, without being asked. That is exactly the property polling could never buy. A message for you arrives at the server, the server writes it down your socket, and it is on your screen. No asking, no gap.
Two housekeeping details make this real. First, a heartbeat: a tiny ping and pong travels over each connection every ten to thirty seconds, so the server notices when a phone has silently vanished — a dead battery, a lost signal — and cleans up. Second, the server itself changes character. It is no longer answering quick requests and forgetting you; its whole job is now holding your connection open. That makes it a different kind of server, so we rename it for what it now does: a chat server. The trade-off we accept is that a chat server is stateful — it holds particular people's live connections, so losing one matters. What that costs, and how we survive it, is the last use case.
Under the hood — how it works
What is this connection, physically? A WebSocket runs on top of TCP. When two machines talk over TCP, the operating system on each end creates a socket — a small in-kernel object that represents that one conversation. Every TCP connection is identified by four numbers together: the source IP address, the source port, the destination IP address, and the destination port. This is called the 4-tuple. When a packet arrives, the operating system reads those four numbers off it and matches the packet to the right open socket. Change any one of the four, and it is a different connection.
A port is a sixteen-bit number, so it runs from zero to about sixty-five thousand. That one fact is the source of a famous myth: 'a server can only hold 65k connections.' Here is where the ceiling really applies. When your phone connects to one server, three of the four numbers are already fixed — the server's IP, the server's port (say 443), and the phone's own IP. Only the phone's source port can vary. So one client can hold at most about sixty-five thousand connections to one server address. That limit is real. But notice who it binds: a client, talking to a single address. No phone needs sixty-five thousand connections to one server.
Now flip to the server side, because this is the part people get wrong in interviews. On every connection a server accepts, the server's own two numbers are the same — its IP and its one listening port. What varies is the client side: billions of possible client IPs, each with its own range of ports. Two clients that happen to use the same source port are still different 4-tuples, because their IPs differ. So the server spends no port per client. It reuses its one listening port for everyone, and the sixty-five-thousand ceiling simply does not apply to accepting connections.
So what does limit a server? Each open connection costs a file descriptor — the small integer handle the operating system gives out per open socket — plus a few kilobytes of kernel memory for buffers. The default file-descriptor limit is low, often around a thousand, but it is just a setting, and you raise it. Give the box enough RAM, and one server holds millions of connections. WhatsApp famously tuned exactly these knobs and held over two million connections on a single machine. The real ceiling is millions, not tens of thousands.
One place the port ceiling does bite a server: outbound connections. The moment a server opens many connections to one other machine, it is the client in that relationship — and it faces the same sixty-five-thousand ceiling toward that single address. Keep that in your pocket. It comes back the moment our servers need to talk to each other in bulk.
What does a chat server actually do all day? It listens on a port. When a phone connects, the OS hands it a fresh socket, and the WebSocket handshake upgrades that socket into a persistent two-way channel. Then the server watches it — along with the half million other sockets it holds — for incoming data. It cannot afford a thread per connection; a million threads is not a thing an operating system will give you. Instead it uses the kernel's event-notification machinery (epoll on Linux, kqueue on BSD): one thread hands the kernel its whole list of sockets and asks, 'which of these have data right now?' The kernel returns just the handful that do. That trick is what lets one box watch a million connections with a few threads.
One small piece completes the picture: with half a million sockets open, how does the server find the right one for a given phone? It keeps an in-memory table mapping each connected device to its socket's file descriptor. On connect, it records the entry. To deliver, it looks up the device, gets the descriptor, and writes the message to exactly that socket. On disconnect, the entry is removed. Plain, per-server, in RAM.
Put together, this explains what actually caps a chat server. Holding idle sockets is cheap — descriptors and RAM, both of which just sit there. But our sockets are not idle. Every message means the kernel waking a thread, bytes being copied, the right connection being found and written to. With connections churning messages, the CPU runs out long before the socket count does. A chat server is CPU-bound, not socket-bound. Hold that thought for the next deep dive, where we size the fleet.