You fire. The crosshair is dead center on the enemy's head. You hear the shot. Nothing happens. They kill you instead. You shout at your screen. But it's not your aim that's wrong, it's the netcode. More specifically, it's the 150-millisecond gap between your reality and the server's reality, and the imperfect systems built to bridge that gap.
Netcode is the catch-all term for the networking layer in multiplayer games. It encompasses tick rates, lag compensation, interpolation, extrapolation, and the philosophical question of whose version of reality counts. Let's break each piece down.
Tick Rate: The Heartbeat of Multiplayer
A tick rate is how many times per second the server updates the game state. A 64-tick server processes inputs and sends updates 64 times per second, roughly every 15.6 milliseconds. A 128-tick server does it twice as fast.
Higher tick rates mean the server captures a more granular picture of what's happening. At 64 tick, a player moving at high speed could be in a noticeably different position between ticks. At 128 tick, the gap shrinks. This matters most in fast-paced shooters where milliseconds determine whether a bullet hits or misses.
// Tick rate comparison
64 tick = 15.6ms between updates → "good enough" for most games
128 tick = 7.8ms between updates → competitive standard
20 tick = 50ms between updates → many battle royales (too many players for higher)
But higher tick rates aren't free. Doubling the tick rate roughly doubles the bandwidth and CPU usage on both server and client. This is why Fortnite runs at ~20 tick (150 players, too expensive to go higher) while Valorant runs at 128 tick (10 players, competitive integrity is paramount).
Lag Compensation: Rewinding Time
Here's the core problem: when you see an enemy on your screen, you're seeing where they were, not where they are. Your screen is always showing the past, specifically, the past by an amount equal to your latency plus the interpolation delay.
If you have 40ms ping, your view of the world is roughly 40-60ms old. In that time, an enemy could have moved behind cover on the server's timeline but still be visible on yours. So when you click their head, who's right? You or the server?
Lag compensation solves this by rewinding time. When the server receives your "I shot at position X" input, it rewinds the game state to match what you were seeing at the moment you fired. It checks the hit against that historical state. If your aim was on target in your timeline, the hit registers.
This is why you sometimes get killed after you've already run behind a wall. On your screen, you're safe. But the player who shot you was seeing you 40ms in the past, when you were still exposed. The server honored their timeline, not yours. Both players experienced a valid version of reality, the conflict is inherent in the latency.
Interpolation vs Extrapolation
Since the server only sends updates at the tick rate, clients need to smooth out the gaps between updates. This is where interpolation and extrapolation come in.
Interpolation renders entities at a position between two known server states. This means the client is always showing a slightly delayed (but smooth and accurate) version of other players. Most games use interpolation with a buffer of 1-2 ticks.
Extrapolation predicts where an entity will be based on its last known velocity and direction. This is more responsive but can be wrong, if a player suddenly changes direction, the extrapolated position will be incorrect until the next server update corrects it, causing a visible "snap."
// Interpolation (smooth but delayed)
renderPosition = lerp(lastServerPos, nextServerPos, timeBetweenTicks);
// Extrapolation (responsive but potentially wrong)
renderPosition = lastServerPos + (lastVelocity * timeSinceLastUpdate);
Most modern games use interpolation for other players (accuracy matters) and client-side prediction for the local player (responsiveness matters). You never want to interpolate the local player, it would feel like playing with input lag.
Client-Side Prediction and Server Reconciliation
Client-side prediction means your local game immediately acts on your inputs without waiting for the server. When you press W, your character moves forward right now. In the background, the client also sends that input to the server and waits for confirmation.
When the server's response arrives, the client compares its predicted state to the server's authoritative state. If they match, great, nothing to do. If they differ (because another player hit you, or the server rejected a move), the client "reconciles" by snapping to the server state and re-simulating all inputs that happened since.
This is why you occasionally get rubber-banded back to a previous position. Your client predicted you'd keep running, but the server disagreed. Good reconciliation makes this seamless; bad reconciliation makes the game feel broken.
Bandwidth and the State Problem
Every tick, the server needs to tell every client about every relevant entity's state. In a 10-player shooter at 128 tick, that's 128 updates/second × 10 players × state payload per player. This adds up fast.
Optimization techniques include:
- Delta compression: Only send what changed since the last update.
- Relevancy filtering: Don't send data about entities the player can't see.
- Quantization: Reduce precision of position data (you don't need 64-bit doubles for coordinates).
- Snapshot interpolation: Send full snapshots at lower frequency, let clients interpolate between them.
Getting bandwidth right is an ongoing battle. Too much data and players with slower connections drop packets. Too little and the game state becomes inaccurate. Most games target 50-100 KB/s per player, roughly the bandwidth of a music stream.
Why Perfect Netcode Doesn't Exist
Here's the truth that every multiplayer developer eventually accepts: perfect netcode is impossible. The speed of light imposes a physical minimum on latency. A player in New York and a player in Tokyo will always have ~80ms between them, even in a perfect world. No amount of prediction, compensation, or interpolation can eliminate that gap, only mask it.
The goal isn't perfect netcode. The goal is netcode that feels fair. Players can tolerate imperfection as long as it feels consistent and predictable. The worst feeling in a multiplayer game isn't high latency, it's inconsistent latency. A stable 80ms is better than jittering between 30ms and 120ms.
Build for fairness, optimize for consistency, and always test with real-world latency conditions. That artificial 5ms connection in your development environment will lie to you.
