Multiplayer and Netcode for Web Games

Updated June 2026
Multiplayer is the most requested and most misunderstood feature in web game development. Building a real-time multiplayer browser game requires understanding transport protocols like WebSockets and WebRTC, choosing the right server architecture, and implementing netcode patterns such as client prediction and lag compensation. This guide covers every layer of the multiplayer stack for browser games, from raw sockets to production-ready frameworks.

What Are Multiplayer Web Games

A multiplayer web game is any browser-based game where two or more players interact in a shared game world through a network connection. This ranges from simple turn-based board games that exchange moves over HTTP to fast-paced first-person shooters running at 60 frames per second with sub-50-millisecond latency requirements. The browser as a platform has matured enough to support both extremes, and everything in between.

What makes web multiplayer distinct from native multiplayer is the transport layer. Native games can open raw UDP sockets, send datagrams freely, and implement their own reliability layer on top. Browsers cannot do this. Web games are limited to WebSockets (TCP-based, reliable, ordered), WebRTC data channels (UDP-like, configurable reliability), and HTTP requests. This constraint shapes every architectural decision you make when building multiplayer for the browser.

The genres that work well in browsers have expanded significantly. Casual multiplayer games like Agar.io, Slither.io, and Diep.io proved that simple mechanics with real-time multiplayer can attract millions of players. More complex examples include Krunker (a browser FPS with competitive matchmaking), BrowserQuest (an open-source MMO demo by Mozilla), and numerous poker, chess, and card game platforms. Three.js and Babylon.js have pushed the graphical fidelity high enough that the browser is no longer just a platform for 2D casual games.

The multiplayer architecture you choose depends entirely on your game's requirements. A chess game needs only a simple relay server that validates moves. A real-time action game needs an authoritative server running the full game simulation, sending state updates 20 to 60 times per second, while the client predicts player movement locally and reconciles with server corrections. A cooperative puzzle game might use peer-to-peer WebRTC connections with no dedicated server at all. Understanding these tradeoffs is the foundation of multiplayer web development.

Transport Protocols for Real-Time Communication

Every multiplayer web game needs a way to send data between players and servers in real time. The browser gives you three options, each with different characteristics that suit different game types.

WebSockets

WebSockets are the most common transport for multiplayer web games. A WebSocket connection starts as an HTTP request that upgrades to a persistent, full-duplex TCP connection. Once established, both client and server can send messages at any time without the overhead of HTTP headers on every message. The connection stays open until one side closes it.

Because WebSockets run over TCP, every message is guaranteed to arrive in order. This is both a strength and a weakness. For a chat system or turn-based game, reliable ordered delivery is exactly what you want. For a fast-paced action game, it creates a problem called head-of-line blocking. If one TCP packet is lost, the operating system holds all subsequent packets until the lost one is retransmitted and received. This can cause sudden latency spikes of 100 to 300 milliseconds, which is devastating for a game running at 60 FPS. Despite this limitation, most multiplayer web games use WebSockets because they are simple, well-supported, and work through every firewall and proxy.

The WebSocket API in browsers is straightforward. You create a connection with new WebSocket("wss://server.example.com"), send messages with ws.send(data), and receive messages through the onmessage event. Messages can be strings or binary (ArrayBuffer). For games, binary messages are strongly preferred because they are smaller and faster to parse. A common pattern is to define a binary protocol where the first byte is the message type and the remaining bytes are the payload, encoded with a schema-based serializer like FlatBuffers, MessagePack, or Protocol Buffers.

WebRTC Data Channels

WebRTC was designed for video and audio conferencing, but its data channel feature is valuable for games. WebRTC data channels can run in unreliable, unordered mode, which behaves like UDP. Packets that arrive late are not held up by lost packets, and lost packets are simply dropped rather than retransmitted. For position updates in a fast-paced game, this is ideal because a stale position update is worthless anyway.

The tradeoff is complexity. WebRTC requires a signaling server to negotiate connections (exchanging SDP offers and ICE candidates), and it needs STUN servers for NAT traversal. If players are behind symmetric NATs (common on mobile networks and corporate firewalls), a TURN relay server is needed, which adds latency and hosting costs. The connection setup process takes several round trips, so it is slower to establish than a WebSocket. WebRTC connections can be peer-to-peer (player to player) or client-to-server, but the peer-to-peer path is the most common use case in games because it eliminates the server from the data path entirely.

HTTP and Server-Sent Events

Plain HTTP requests and Server-Sent Events (SSE) are viable for slower-paced games. A turn-based game can simply POST each move to a REST endpoint and use SSE or polling to receive opponent moves. This approach is the simplest to implement and deploy because it uses standard HTTP infrastructure. The latency is too high for real-time action games, but for card games, board games, and strategy games with turns lasting seconds or minutes, it works well and scales easily with standard web infrastructure.

Server Architecture Patterns

The server architecture defines who is responsible for the game simulation and how game state flows between participants. This decision has the biggest impact on cheating resistance, latency, and hosting costs.

Authoritative Client-Server

In an authoritative server model, the server runs the complete game simulation. Clients send their inputs (key presses, mouse movements, action commands) to the server, and the server applies those inputs to the simulation, then sends the resulting game state back to all clients. The server is the single source of truth. If a client claims it moved 100 units in one frame, the server ignores that claim and applies the movement rules itself.

This is the gold standard for competitive multiplayer games because it makes most forms of cheating impossible. A client can modify its local game code however it wants, but the server will reject any input that violates the game rules. Speed hacks, teleportation, and damage modification all fail because the server never trusts client-reported state.

The cost is latency. Every player action must make a round trip to the server before the result is confirmed. Without client-side prediction (covered in the netcode section below), the game would feel sluggish even on good connections. Running the full simulation on the server also means higher hosting costs, since each game room needs CPU time proportional to the simulation complexity.

Relay Server

A relay server forwards messages between clients without understanding or validating them. Each client runs the game simulation locally, and the relay simply broadcasts inputs or state from each client to all others. This is simpler to implement because the server has no game logic, but it offers zero cheat protection. Any client can send falsified state, and the relay will happily forward it to everyone else.

Relay servers work well for cooperative games where cheating is not a concern, or for prototyping multiplayer mechanics before investing in server-side game logic. They are also cheaper to host because they do very little processing.

Peer-to-Peer

Peer-to-peer multiplayer removes the dedicated server entirely. Players connect directly to each other using WebRTC data channels. One player may be designated as the "host" and run the authoritative simulation, or all players may run deterministic lockstep simulations where they exchange inputs and each client computes the same result independently.

The main advantage is cost. There is no server to host, so the game can scale to many concurrent sessions without any infrastructure spending. The disadvantages are significant. NAT traversal fails for some players, the host has zero latency (giving them an unfair advantage in competitive games), and if the host disconnects, the game ends or requires a host migration protocol. Peer-to-peer is best suited for small-session games (2 to 4 players) and cooperative or casual experiences where competitive fairness is less critical.

Hybrid Approaches

Many games combine these patterns. A common hybrid uses an authoritative server for critical game state (health, score, item ownership) while allowing peer-to-peer connections for cosmetic or non-critical data (voice chat, emotes, non-gameplay animations). Another hybrid runs an authoritative server for competitive ranked modes and a simple relay for casual modes, reducing hosting costs for the less competitive player base.

Netcode Fundamentals

Netcode is the collection of techniques that make a networked game feel responsive despite the reality of network latency. A player in New York connecting to a server in Frankfurt has a minimum round-trip time of roughly 80 milliseconds due to the speed of light through fiber optic cables, and real-world latency is usually 100 to 200 milliseconds after routing, processing, and buffering. Without netcode techniques, the game would feel unplayable. These patterns are what make online games feel smooth.

Client-Side Prediction

Client-side prediction means the client does not wait for the server to confirm an action before showing the result. When a player presses the right arrow key, the client immediately moves the character to the right based on the same movement rules the server uses. The input is also sent to the server, which processes it and sends back the authoritative result. If the client's prediction matches the server's result (which it usually does), the player sees smooth, instant movement with no perceptible latency.

When the prediction does not match the server's result, the client must correct itself. This is called server reconciliation. The client replays all unacknowledged inputs from the point of the server correction forward, snapping to the corrected state and re-predicting from there. A well-implemented reconciliation is invisible to the player most of the time, with corrections appearing as slight rubber-banding only during extreme lag or when the prediction logic diverges from the server logic.

Entity Interpolation

While the local player's movement is predicted, other players' positions come from the server. If you simply render other players at their last known server position, their movement appears jerky because server updates arrive at 10 to 30 times per second while the game renders at 60 FPS. Entity interpolation solves this by rendering other entities slightly in the past, smoothly interpolating between the two most recent server positions. This adds a small amount of visual delay (typically one server tick, or 33 to 100 milliseconds) but produces smooth movement that looks natural.

Lag Compensation

Lag compensation addresses the problem of shooting at a moving target in a latent environment. When a player fires at another player, they aim at where they see the target on their screen. But due to interpolation and network delay, the target's actual position on the server is different from what the shooter sees. Lag compensation solves this by rewinding the server's state to match what the shooter was seeing at the time they fired, checking the hit against that historical state, and then applying the result to the current state. This makes shooting feel fair and responsive for the attacker, though it can occasionally cause the defender to be hit after they thought they moved behind cover.

State Synchronization

Keeping game state synchronized across all clients is the central challenge of multiplayer netcode. There are two main approaches. Full state synchronization sends the entire game state to every client on every update. This is simple and self-correcting (any desync fixes itself on the next update) but bandwidth-intensive. Delta compression sends only the changes since the last acknowledged state, dramatically reducing bandwidth but requiring reliable tracking of what each client has received.

For web games, bandwidth is a real constraint. Mobile players on cellular connections may have bandwidth caps, and even broadband players can experience congestion. A typical approach is to send full state infrequently (every few seconds) as a baseline, with delta updates at the normal tick rate. Interest management further reduces bandwidth by only sending state for entities that are relevant to each client, such as nearby players and objects within the player's field of view.

Tick Rate and Update Frequency

The tick rate is how many times per second the server processes inputs and updates game state. A higher tick rate means more responsive gameplay but higher CPU and bandwidth costs. Most competitive web games run at 20 to 30 ticks per second, which provides a good balance. Casual games can run at 10 ticks per second or lower. The tick rate does not need to match the client's frame rate because interpolation and prediction fill the gaps between server updates.

Choosing a Multiplayer Framework

Building multiplayer from raw WebSockets is educational but impractical for most projects. Several frameworks handle the boilerplate of room management, state synchronization, and connection handling so you can focus on game logic.

Colyseus

Colyseus is the most popular open-source multiplayer game framework for JavaScript and TypeScript. It runs on Node.js and provides automatic state synchronization using a schema-based approach. You define your game state as Colyseus Schema classes, and the framework automatically tracks changes, serializes them efficiently, and sends delta updates to clients. It handles room creation, matchmaking, reconnection, and scaling across multiple Node.js processes. Colyseus is well-suited for web games because both the server and client are JavaScript, making it easy to share game logic between them.

Socket.IO

Socket.IO is a general-purpose real-time communication library, not a game framework. It provides WebSocket connections with automatic fallback to HTTP long-polling, reconnection handling, and room-based message broadcasting. Many developers reach for Socket.IO because they already know it from web development, but it lacks game-specific features like state synchronization, input handling, and tick-based processing. It works for turn-based or slow-paced games but requires significant custom code for real-time action games.

Nakama

Nakama is an open-source game server written in Go. It provides matchmaking, real-time multiplayer, leaderboards, chat, and user accounts out of the box. Its real-time multiplayer uses WebSockets and supports both authoritative and relayed match modes. Nakama's JavaScript client SDK works in browsers, making it viable for web games. The Go server is significantly more performant than Node.js for CPU-intensive game simulations, which matters for physics-heavy games or large player counts per session.

Managed Services

Cloud services like Photon, PlayFab, and Amazon GameLift handle multiplayer infrastructure so you do not manage servers at all. Photon offers a JavaScript SDK for browser games and provides relay and authoritative server modes with global server deployment. These services charge per concurrent user or per message, which can become expensive at scale but eliminates the operational burden of managing servers, handling DDoS attacks, and scaling capacity.

Scaling Multiplayer Infrastructure

A multiplayer game server is fundamentally different from a web application server. Web applications are stateless, a request can be handled by any server behind a load balancer. Game servers are stateful, a player's WebSocket connection must stay attached to the specific server process running their game room for the entire session. This makes scaling multiplayer infrastructure more complex than scaling a typical web app.

Room-Based Architecture

Most multiplayer games organize players into rooms (also called sessions, matches, or instances). Each room runs independently with its own game state and tick loop. A server process can host multiple rooms simultaneously, limited by CPU and memory. When a server is full, new rooms are created on a different server. A matchmaking service or lobby server tracks which rooms exist, which have open slots, and routes new players to the appropriate server.

Horizontal Scaling

Scaling horizontally means running more server processes across more machines. The challenge is coordination. A matchmaking service must know the capacity of every server, route players to servers with available capacity, and handle server failures gracefully (migrating rooms or ending matches). Frameworks like Colyseus provide built-in presence and matchmaking services that coordinate across multiple Node.js processes. For custom solutions, Redis is commonly used as the coordination layer because it provides pub/sub, atomic operations, and fast key-value lookups.

Geographic Distribution

Latency is dominated by physical distance. A player in Tokyo connecting to a server in Virginia will always have at least 150 milliseconds of round-trip latency regardless of how fast the server is. For real-time games, you need servers in multiple geographic regions. Players should be matched with others in their region whenever possible, and connected to the nearest server. This adds operational complexity but is essential for any real-time game with a global player base.

Common Pitfalls and How to Avoid Them

Multiplayer web game development has several recurring mistakes that catch developers off guard. Knowing them in advance saves significant debugging time.

Trusting the Client

The most common and most serious mistake is running game logic only on the client and trusting the results. If the client reports "I killed player X," the server should not accept that claim. The server must validate every action against its own simulation. Any computation that affects game outcomes must happen on the server. Client-side code is fully visible and modifiable by players through browser developer tools.

Ignoring Bandwidth Costs

Sending JSON-encoded game state 30 times per second to 20 players in a room consumes significant bandwidth. A single JSON state update might be 2 KB. At 30 updates per second to 20 players, that is 1.2 MB per second per room. With 100 concurrent rooms, you are pushing 120 MB per second from your server. Binary serialization (MessagePack, FlatBuffers, or custom binary protocols) typically reduces message sizes by 60 to 80 percent. Delta compression reduces them further. Always measure your bandwidth usage early in development and optimize before it becomes a production crisis.

Not Handling Disconnections

Players disconnect constantly. Their browser tab loses focus, their WiFi drops, their phone switches from WiFi to cellular. A multiplayer game must handle disconnections gracefully. This means detecting disconnections quickly (WebSocket ping/pong frames with short timeouts), allowing reconnection to an active game session within a grace period, pausing or AI-controlling the disconnected player's character, and cleaning up abandoned sessions to free server resources.

Synchronizing Time

Client clocks are unreliable. Two players' system clocks can differ by seconds or even minutes. Any netcode that depends on timestamps (lag compensation, input sequencing, animation synchronization) must use server-relative time. A common approach is to measure the round-trip time between client and server during connection, compute the clock offset, and use server time plus the offset for all game-related timestamps.

Testing Only on Localhost

Multiplayer games that work perfectly on localhost often break immediately when tested over real networks. Localhost has zero latency, zero packet loss, and infinite bandwidth. Real networks have 20 to 200+ milliseconds of latency, occasional packet loss, and bandwidth limitations. Use network simulation tools (Chrome DevTools throttling, or tools like clumsy on Windows, tc on Linux) to test with artificial latency and packet loss during development. Test with players in different geographic regions before launching.

Explore This Topic