This episode builds a multiplayer game: a client-server architecture with an authoritative server, state synchronization with delta compression, input handling, latency mitigation with client prediction, and game-specific optimization.

Multiplayer games are the hardest test for WebSocket: every millisecond counts, cheating must be prevented, and players on different networks must see the same world. Failures here are felt instantly — characters teleporting, shots not hitting their target.
Episode 23 covers multiplayer game basics: an architecture with an authoritative server, state synchronization, input collection, latency mitigation, and the optimization that lets 60 players play smoothly in one world.
The rules run on the server, not the client. The client only sends input and receives the official state.
client input -> server memvalidasi -> server update state -> broadcastAn authoritative server prevents cheating: a player cannot just send "I moved here". The client sends "right key pressed", the server computes where the character moves and announces it.
P2P eliminates server cost, but creates problems: one cheating player can affect everyone, and syncing among N players becomes N-squared connections. For small-scale games, a simple authoritative server is the best balance.
Games run in fixed time steps called ticks.
const TICK_RATE = 20; // 20 tick per detik
let tick = 0;
setInterval(() => {
tick += 1;
for (const pemain of pemainList) {
prosesInput(pemain, tick);
updateFisika(pemain, tick);
}
broadcastState(tick);
}, 1000 / TICK_RATE);TICK_RATE = 20 means the state is computed 20 times per second and broadcast every tick. All players see the same tick, so the world runs consistently for everyone.
Sending every player's position every tick balloons. Send only what changed.
function broadcastState(tick) {
const delta = [];
for (const pemain of pemainList) {
if (pemain.posisi !== pemain.posisiTerakhir) {
delta.push({
id: pemain.id,
x: Math.round(pemain.posisi.x * 10) / 10,
y: Math.round(pemain.posisi.y * 10) / 10,
});
pemain.posisiTerakhir = pemain.posisi;
}
}
io.emit("state", { tick, delta });
}Math.round(... * 10) / 10 rounds the position to one decimal — enough for visuals, much smaller than full floats. Players who are still do not get sent at all.
Input from clients arrives irregularly; the server buffers it, then processes per tick.
const buffer = new Map();
socket.on("input", (input) => {
input.tick = socket.tickTerakhir;
const list = buffer.get(socket.id) || [];
list.push(input);
buffer.set(socket.id, list);
});
function prosesInput(pemain, tick) {
const inputs = (buffer.get(pemain.id) || [])
.filter((i) => i.tick <= tick);
for (const input of inputs) {
terapkan(input);
}
buffer.delete(pemain.id);
}Input carrying a tick number lets the server order it correctly. Validation stays on the server: maximum speed, plausible positions, and actions allowed by the game state.
Without prediction, the player's character feels like it slides with one latency of lag. Client prediction runs the movement on the client first, then corrects when the server state arrives.
function inputKiri() {
// jalankan lokal untuk respons instan
pemainLokal.x -= KECEPATAN / 60;
kirimKeServer("input:kiri");
}The client moves the character instantly for responsiveness, then the server sends the official state to compare against. If the server state differs from the prediction, the client corrects — usually imperceptibly because the difference is small.
For other players' characters, do not jump to the new position — interpolating between two snapshots makes the movement smooth. Dead reckoning extrapolates the position from the last velocity, so opponents keep appearing to move even when a packet is briefly lost.
Players do not need to see the whole map.
function tetangga(pemain) {
return pemainList.filter((p) => {
const dx = p.x - pemain.x;
const dy = p.y - pemain.y;
return dx * dx + dy * dy < 40000; // radius 200 unit
});
}tetangga(pemain) limits broadcasts by distance — the Area of Interest (AOI) pattern. A player on the left side of the map does not receive updates from a player on the right side, drastically cutting network load.
Not all updates are equal: the position of a player you are dueling matters more than a distant enemy. Prioritize per update and send the most urgent ones when bandwidth is limited.
Episode 23 revealed the secret of multiplayer games that feel smooth: an authoritative server enforces the rules, ticks divide time, delta and AOI save the network, and prediction and interpolation hide latency.
Key takeaways:
In the next episode we cover file transfer over WebSocket: chunked transfer, binary data, progress tracking, and chunk size and compression optimization.