How .io Games Actually Work: WebSockets, Authoritative Servers, Client Interpolation, and the Netcode Behind Browser Multiplayer
If you have ever wondered how Agar.io can have fifty live players in a single room, respond to your mouse in less than 100ms, stay smooth even on a laggy wifi connection, and load in under two seconds — the answer is not any one clever trick. It is a stack of specific architectural decisions, each of which solves a real problem in real-time browser multiplayer. Almost all of those decisions are shared across every .io game worth playing, which is why the genre feels consistent even though hundreds of different teams have built these games.
This piece is the technical complement to our earlier essay on why .io games succeeded as a genre. That piece was about players and design; this piece is about packets and timers. If you have ever thought about building a browser multiplayer game, or you want to understand why some .io games feel great and others feel like they are underwater, this is the vocabulary you need.
The transport: why WebSocket and not HTTP
A regular browser page communicates with its server over HTTP: client opens a connection, sends a request, server sends a response, connection closes (or is held open for a next request). The model works fine for loading a webpage. It is catastrophic for multiplayer.
The problem is server-initiated messages. In a multiplayer game, the server has information the client needs to know about — another player's move, a spawned enemy, a scored point — and the client did not ask for it. HTTP, fundamentally, has no mechanism for the server to push data to a client that did not request it. Long-polling and Server-Sent Events hack around this, but both have overhead and latency penalties that make them unsuitable for 60-Hz-scale real-time play.
WebSocket, standardised in 2011 (RFC 6455), solves this. A WebSocket connection starts as a normal HTTP request with an Upgrade: websocket header; the server upgrades the connection into a long-lived, bidirectional TCP-backed channel; after the upgrade, both sides can send messages whenever they want, with no per-message request-response overhead.
For an .io game this is perfect. The client opens one WebSocket, the server opens one WebSocket per connected player, and the game loop on both sides pushes messages into the open channels whenever something changes. A typical .io game sends 20-60 server ticks per second to each client, and 10-30 input events per second from each client to the server, all over the same two-way pipe that was opened at page load.
A modern browser-multiplayer game could use WebRTC DataChannels instead of WebSocket, which use UDP under the hood and let you trade reliability for latency. In practice, .io games almost all use WebSocket, because WebSocket has near-universal browser support, easier infrastructure (no STUN/TURN servers), and TCP's head-of-line blocking is tolerable at the small message sizes .io games send. WebRTC is the frontier; WebSocket is the standard.
The authoritative server
The single most important architectural decision in any multiplayer game is: who decides what happened?
The naive answer is "both players." Player A tells the server they moved, Player B tells the server they moved, the server just relays the messages. This is a peer-to-peer or lockstep model, and it is used in some fighting games and real-time strategy games. For .io games, it is a disaster waiting to happen, because the client is trusted to report its own state — and if the client is cheatable, it will be cheated.
The .io standard is the authoritative server. The server is the only thing that can advance game state. The client reports inputs (mouse position, button presses), and the server integrates them into its simulation. When the server has a new state to share, it sends it out to everyone. Clients never write to game state directly; they render whatever the server tells them.
This architecture has three consequences.
First, cheating is harder. A modified client can still send inputs faster than a human could, but it cannot teleport or give itself infinite mass, because the server simulation rejects those moves. This is not perfect — inputs like mouse coordinates can still be scripted — but it closes the worst exploits.
Second, latency becomes visible. Because the server decides what happened, and the server is physically far away, the client cannot move instantly. You click to move, the click takes 30ms to reach the server, the server simulates a tick, the result takes another 30ms to come back, and only then does your character move on screen. 60-120ms of input lag is enough to feel terrible. This is the problem that the next two sections solve.
Third, the server needs to be fast. An authoritative server is simulating the entire game world on its own CPU, often 30-60 times per second, for hundreds of concurrent players. The game-design choices that make .io games work — small entity counts per room, simple physics, low-dimensional state — are not incidental; they are what let one commodity server handle hundreds of simultaneous players.
Client-side prediction
The fix for "input lag makes the game feel terrible" is client-side prediction. The idea is simple: when the player presses a key, the client does not wait for the server to confirm. The client runs its own copy of the simulation immediately, predicts what the server will say, and shows that prediction to the player. The server eventually replies with the authoritative truth; the client reconciles.
In practice, this means the client has two game states at any moment: the predicted state, which reflects the player's inputs as if they had happened instantly, and the authoritative state, which reflects what the server has confirmed. The client renders the predicted state because it feels responsive.
The reconciliation step is the subtle part. When the server sends back the authoritative state from 60ms ago, the client has to check: "did my prediction match what the server said?" If yes, great, nothing to do. If no — say, the player got eaten by another player during the 60ms — the client has to roll back, apply the server's correction, and re-run any subsequent predicted inputs to catch up. This is sometimes called client-side reconciliation or prediction rollback, and it is what makes the player's own avatar feel instant despite living on a remote server.
The classic .io implementation only predicts the player's own character, not other players. Your own movement is snappy; other players' movements come through the next mechanism.
Interpolation and extrapolation for other players
Other players' positions arrive from the server at the server tick rate (say, 20 Hz). A naive implementation would jump each other player to their new position every 50ms, and the visible result would be a room full of stuttering enemies.
The fix is interpolation. The client stores each received position in a buffer, delays its own rendering of other players by a small amount (typically 100-200ms), and smoothly interpolates between the two most recent known positions. Yes, you are seeing other players as they were 100-200ms ago — but you are seeing them move smoothly, which is perceptually much better than jittering in real-time.
The interpolation delay is one of the most important tunables in .io netcode. Too short, and the client catches up to the server and you run out of buffer (enemies appear to stutter). Too long, and the delay makes the game feel laggy even when the underlying connection is fast. 100-200ms is the sweet spot for most browser .io games.
When the buffer does run out — because the server is slower than expected, or a packet was dropped — the client falls back to extrapolation: it keeps moving the other player in the direction they were last moving, guessing where they might be. This can be wrong (the enemy might have turned), but being wrong for 50ms and then snapping to the correct position is less jarring than pausing the enemy mid-run.
Good .io games blend these techniques with different priorities for different object types. The player's own avatar is predicted. Other players are interpolated. Projectiles might be extrapolated (they move in a predictable line). The chat log is not interpolated at all (it just arrives).
Lag compensation
Here is a problem that interpolation creates. Say you shoot another player. On your screen, they are at position X (where they were 100ms ago, per your interpolation buffer). Your shot hits them at X on your screen. But by the time your shot report reaches the server, the server knows the target has moved to Y. Should the server register the hit at X (where you saw them) or Y (where they actually are)?
If the server says "not a hit, they are at Y" — which is technically correct — the game feels broken. You shot them, you saw the hit, you got denied. This happens constantly over a high-latency connection.
The fix is lag compensation. The server keeps a short history of recent world states (maybe the last 500ms). When the client reports a shot, the server checks when the shot was fired, rewinds to the world state the client was seeing at that moment, and resolves the hit against that state rather than the current one. If the shot would have hit in the rewound state, the server confirms the hit.
This is, technically, letting a player affect an enemy that has already moved. Which feels unfair to the enemy (they got hit by something that was aimed at where they were). Different .io games resolve this differently: some fully compensate, some partially, some not at all. The tension between "the shooter saw a hit and it counts" and "the target should not be punished for the shooter's latency" has no single right answer; it is a design choice dressed up as a technical one.
State synchronisation: what the server actually sends
A common bug in naive .io servers is sending the whole world state every tick. For a 30-Hz server with 50 players and 200 objects, that is a lot of repeated data. Over 30 ticks a second, over dozens of players, the bandwidth balloons to the point where it limits your player count.
Production-quality .io servers use two techniques to cut this down:
Delta compression. The server sends only the changes since the last confirmed snapshot. Each player's client remembers the last snapshot they acknowledged; the server sends only what has changed since then. A player who did not move contributes nothing to the next packet. On a typical server tick, maybe 10-15% of the world actually changed; delta compression reflects that.
Area-of-interest culling. A player on the east side of the map does not need to know about a player on the west side. The server maintains a spatial index (a grid, a quadtree, or a per-player visibility set), and each client only receives updates for entities near them. Agar.io-scale rooms with 50+ simultaneous players are only possible because each player gets a narrow slice of the full world state per tick.
Good servers also prioritise updates: closer entities get more frequent updates, farther entities get less frequent. A .io server that updates every entity at the same rate is wasting its bandwidth on objects you cannot see.
Tick rates and the size-vs-smoothness trade-off
The server tick rate — how many times per second the simulation advances and pushes updates — is the single biggest knob in .io netcode. Higher tick rates feel smoother; lower tick rates let you support more players per server.
Typical .io games run at 20-60 Hz. Competitive shooters might go to 120 Hz. Mobile-friendly simple games might drop to 10 Hz. At 10 Hz you need aggressive interpolation to hide the gaps; at 60 Hz you can interpolate less and still feel smooth. Each doubling of tick rate roughly halves the players per server, which is why most .io games settle on 20-30 Hz as the economic sweet spot.
A related knob is the update packet size. Bigger packets carry more info per tick but cost more bandwidth. .io servers often send full-fidelity positions for close entities and compressed (lower-precision) positions for far entities — the player cannot see them precisely anyway, so 8-bit coordinate deltas are fine.
Why .io games feel so specific
The techniques above are standard in most online games. What makes .io games feel distinct is the combination of constraints the browser imposes:
- No UDP. Browser games are TCP/WebSocket only, so you have head-of-line blocking and cannot "skip" a dropped packet. You tune around this by keeping packets small and sending frequent updates.
- No native-code client. The client simulation has to run in a JavaScript engine, which is fast but not deterministic in the strict sense required for perfect lockstep. So almost all .io games are server-authoritative, not peer-to-peer.
- Tab-first assumptions. A player who tab-switches, sleeps their laptop, or has a flaky wifi connection is common. .io games are designed to gracefully handle disconnects (your entity stays in the world for a few seconds, then is removed), and re-joining is usually a one-click affair rather than a full matchmaking cycle.
- Browser discoverability. No download, no install, no account. The tech stack is designed to load the whole client in a couple of seconds on a cold page and drop the player into a live server immediately.
The combination produces the characteristic .io feel: fast to start, tolerant of bad connections, playable on anything with a browser, responsive enough to feel competitive. The genre's tech decisions are visible in every round you play.
What you can play on PhyFun
The .io games in our catalog follow the architecture described above to varying degrees. The ones we keep all implement at least: WebSocket transport, authoritative server, interpolation for other players. The best ones also do prediction and lag compensation.
For catalog entry points into the genre:
- Spinner Io — classic growth-based .io; good example of the base mechanics.
- ColorWars.io — territory-capture .io, a sub-variant with different strategic shape.
- Stickman Broken Bones .io — ragdoll-physics meets multiplayer, a hybrid that only works because of the netcode described above.
- Earthquake .io — destruction-themed .io.
- MergeDuel.io — merge mechanics against a live opponent, one of the newer .io hybrids.
- Kings.io — realm-building .io.
- Paper Wars: Battles and Upgrades — strategy-leaning multiplayer with .io-style rooms.
- Tank Arena and Fire and Water — arena-style multiplayer.
Our full IO category has more; if you want to sample the breadth of the architecture in a single session, play two or three of the above and notice how the feel is similar despite the games being quite different. That consistency is the architecture showing through the gameplay.
For adjacent reading: our essay on why the .io format won, our history of the Flash-to-HTML5 transition (which made the .io genre possible in the first place), and our physics games deep dive for the single-player counterpart.
The .io genre is, more than any other format, a product of browser-specific technology. A native multiplayer game can choose UDP, binary protocols, custom net libraries; an .io game has to live within WebSocket and JavaScript and make it feel great anyway. The games you play in a tab are the ones that figured out how.