Multiplayer Netcode
Netcode exists to solve one problem: the authoritative version of the game is somewhere else, and light is slow. A player in Berlin talking to a server in Virginia has an unavoidable round trip of roughly 90 ms. If pressing a movement key waits for the server to confirm it, the game feels broken — and if it doesn't wait, the client and the server disagree about reality.
Everything in this field is a technique for hiding that gap while keeping the server in charge. Client prediction hides your own latency. Entity interpolation hides everyone else's. Lag compensation makes shooting at where you saw someone actually work. Rollback makes fighting games playable across a continent. They compose into a small number of well-understood models, and choosing among them is the first architectural decision a multiplayer game makes.
TL;DR
- The server is authoritative. Anything the client decides is a suggestion; anything the client sends is untrusted input.
- Client prediction: apply your own input locally and immediately, then reconcile when the server's answer arrives.
- Reconciliation: keep an input buffer, replay unacknowledged inputs from the server's corrected state.
- Entity interpolation: render other players ~100 ms in the past, between two received snapshots, so they move smoothly.
- Lag compensation: the server rewinds hitboxes to what the shooter saw. It's why you die behind cover.
- Rollback (fighting games): predict opponent input, re-simulate on mismatch. Requires a deterministic simulation.
- UDP, not TCP — head-of-line blocking makes TCP a poor fit for real-time state.
- Send deltas against acknowledged state, not full snapshots; use interest management to cull what each client needs.
Quick Example
Prediction and reconciliation — the core client loop of almost every action game:
Note that simulate() is the same function on both sides. Prediction only works if the client can reproduce the server's math exactly.
Core Concepts
Server authority
Clients send inputs, not outcomes. This single rule eliminates the majority of cheats: speed hacks, teleporting, infinite ammo, and damage modification all become server-side validation problems rather than possibilities. It also means the server must run the game simulation, which is the main cost of the model.
The three latency-hiding techniques
They apply to different entities. Your character is predicted. Everyone else is interpolated. Hit detection is compensated. Mixing them up — predicting other players, or interpolating your own character — produces exactly the artifacts each technique exists to prevent.
Prediction and reconciliation
Prediction alone drifts: the client and server will disagree, because of collisions with entities the client hasn't seen, or dropped packets, or float differences. Reconciliation is the correction.
The player perceives nothing if corrections are small. Large corrections need smoothing — blend toward the corrected position over a few frames rather than snapping — but smoothing large errors instead of fixing their cause just delays the visible problem.
Entity interpolation
Snapshots arrive at the tick rate (say 20 Hz, every 50 ms) while you render at 60+ FPS. Rendering the newest snapshot directly gives 20 Hz stutter. Instead, buffer snapshots and render at now - delay, interpolating between the two that bracket that time.
If the buffer runs dry (packet loss), you must extrapolate briefly or freeze. Extrapolation overshoots on direction changes; most engines extrapolate for a short window and then hold.
Lag compensation
The consequence of interpolation: when you fire at an enemy's head, the enemy is 100 ms further along than what you saw. Naive hit detection means every shot at a moving target misses.
This is why you sometimes die after reaching cover — on the shooter's screen, you were still exposed. It's not a bug; it's the deliberate choice to favor the shooter's experience. The alternative (favoring the target) means shots that visually connect don't register, which players find far worse. Cap the rewind window (typically ~200–250 ms) so high-latency clients can't exploit it.
Network Models
Deterministic lockstep
Send only inputs; every peer runs the identical simulation. Bandwidth is independent of entity count — which is why StarCraft and Age of Empires could network thousands of units over dial-up. The costs are severe: the simulation must be bit-deterministic across all machines, and every peer waits for the slowest, so input latency equals the worst ping in the session. One desync and the game is unrecoverable.
Rollback
Rollback fixes lockstep's latency by refusing to wait. Predict the remote player's input (usually "same as last frame"), simulate immediately, and when the real input arrives and differs, rewind to that frame and re-simulate everything since.
Because misprediction is only visible during the ~2–5 frames of correction, and most frames in a fighting game have no input change, rollback feels close to local play at surprising latencies. It requires a fully deterministic, cheaply re-simulatable game state — which is why it's near-universal in fighting games and rare elsewhere.
Bandwidth and Scale
Delta compression
Send only what changed since the client's last acknowledged snapshot. A player standing still costs nothing. Requires keeping a per-client history of acknowledged states, which is the standard price of admission.
Interest management
A client only needs entities it can perceive. Cull by distance, room, or visibility (a grid or spatial hash over the world) so a 100-player match doesn't send every client all 99 others. This is what makes large player counts feasible at all, and it has an anti-cheat benefit: information the client never receives cannot be extracted by a wallhack.
Quantization
Full-precision floats are wasteful. Positions compress to 16-bit fixed-point over a known world range, rotations to a smallest-three quaternion in ~29 bits, and velocities to fewer bits still. Typical savings are 50–75% of state payload for imperceptible precision loss.
Tick rate
Tick rate is a server CPU and bandwidth cost multiplied by every player in every match. Raising it improves responsiveness with diminishing returns; interpolation and prediction quality usually matter more than the last 30 Hz.
Best Practices
Design for multiplayer from the first prototype
Retrofitting authority onto a codebase that assumed local, immediate state changes generally means rewriting the gameplay layer. Even in single-player, separating "simulation state the server owns" from "presentation the client renders" costs little and makes the port tractable.
Share one simulation implementation between client and server
Prediction only works if simulate(state, input) produces identical results on both sides. Two implementations will diverge, and the divergence appears as unexplained rubber-banding. Compile the same code for both, or run JavaScript/C# on both ends.
Validate every input server-side
Range-check movement magnitude, enforce fire-rate cooldowns, verify line of sight, and reject inputs with implausible timestamps. Sanity checks on the server are the entire anti-cheat story for state manipulation.
Never send information the client shouldn't have
Position data for enemies behind a wall becomes a wallhack no client-side rendering rule can prevent. Interest management is a security control as much as a bandwidth one.
Use UDP with your own reliability layer
TCP's in-order guarantee causes head-of-line blocking: a lost packet stalls newer state that's already arrived and would have been more useful. Real-time state wants "newest wins," which is UDP semantics. Use an existing transport (ENet, Steam Networking, QUIC, WebRTC data channels in browsers) rather than writing your own.
Measure with real network conditions
Everything works on localhost. Use a network conditioner to inject 100 ms latency, 50 ms jitter, and 2% packet loss during normal development — not as a final test. Most netcode bugs are invisible at 0 ms.
Make the correction visible to you and invisible to the player
Log and graph reconciliation error magnitude. A rising error rate is the earliest signal that client and server simulation are drifting, and it's much easier to debug from a metric than from a player report that "it feels weird."
Common Mistakes
Trusting client-reported state
Waiting for the server before moving
Interpolating your own character
Sending full state every tick
Building on a non-deterministic simulation, then adding rollback
Rollback and lockstep both require bit-identical re-simulation. Discovering that your physics isn't deterministic after building the netcode on the assumption that it is means starting over. Verify determinism first — run the same inputs twice and compare state hashes.
Ignoring packet loss in testing
FAQ
Which model should I choose?
Match it to the genre. Fast action with a handful of players (FPS, action, battle royale): authoritative client-server with prediction, interpolation, and lag compensation. Fighting games: rollback. RTS with thousands of units: deterministic lockstep. Slow or turn-based: simple client-server without prediction — you don't need the complexity. Co-op with friends: a listen server is often entirely sufficient.
How much latency can I hide?
Prediction hides your own input latency essentially completely up to a few hundred milliseconds — what breaks down is correction size, since more elapsed time means more divergence to fix. Interpolation costs a fixed ~100 ms of "seeing the past," which players don't perceive. Lag compensation works well to roughly 200 ms and starts producing unfair-feeling outcomes for the target beyond that, which is why servers cap the rewind window.
Why do I die behind cover?
Lag compensation, working as designed. The shooter saw you exposed; the server rewound to that moment and confirmed a hit; by the time you see the result, you've reached cover on your screen. The alternative — favoring the target — means shots that visually hit don't register, which testing consistently shows players find more frustrating. Every competitive shooter makes this same tradeoff.
Can I do this in a browser?
Yes. WebRTC data channels give you unreliable, unordered UDP-like delivery in the browser, and WebSockets work for slower-paced games where TCP's head-of-line blocking is tolerable. WebTransport over QUIC is the emerging, cleaner option. The techniques are identical; the transport is the only thing that changes.
What about peer-to-peer?
It removes server cost and can lower latency between nearby players, at the price of cheat resistance (no trusted authority), NAT traversal complexity, and exposing players' IP addresses to each other. It's reasonable for co-op and trusted lobbies, and inappropriate for competitive play or anything with persistent progression.
Do engines handle this for me?
Partially. Unreal has the most complete built-in solution — replication, RPCs, and prediction in CharacterMovementComponent. Unity has Netcode for GameObjects plus third-party options like Mirror and Fish-Net. Godot has high-level multiplayer RPCs and synchronizers. All of them give you transport, replication, and often basic prediction; none of them make the authority and game-feel decisions for you, and those are where the difficulty actually lives.
Related Topics
- Game Loops & ECS — Fixed timesteps as the basis for prediction and rollback
- Game Physics — Determinism requirements for lockstep and rollback
- Unreal Engine — Built-in replication and server authority
- Unity — Netcode for GameObjects and third-party stacks
- TCP/IP — Why UDP rather than TCP
- HTTP/3 & QUIC — WebTransport as a browser UDP path
- WebRTC — Browser data channels for real-time games
- WebSockets — Suitable for slower-paced multiplayer
- Distributed Systems — Consistency and ordering, in another form