Gaming
Design Design PUBG
01
The problem
The problem
A battle royale match puts 100 people on one island for about half an hour. They parachute in, loot buildings, drive vehicles, shoot each other from a few metres or from half a kilometre, and a closing circle herds the survivors together until one squad is left. Every one of those players needs to believe that what they see on screen is what really happened.
Two very different systems sit behind that experience. The platform is ordinary backend work: accounts, parties, matchmaking, store, stats, patches. The match itself runs on a dedicated server process that owns the simulation, advances it in fixed steps called ticks, and streams the result to every client 30 times a second. That process lives for one match and then disappears.
The hard part is the second half. Players are spread across home connections with 20 to 150 ms of round trip and occasional packet loss, and the game still has to feel instant when you press a key and fair when someone dies.
How does one server keep 100 players, thousands of loot items, vehicles, grenades and a moving zone consistent for everyone, at under one frame of perceived input delay, while the network drops and reorders packets, and while some players are actively trying to cheat?
02
What the game needs
What the game needs
- Players queue solo, as a duo or as a squad of four, pick a region and a mode, and get placed into a 100-player match within about a minute.
- Inside the match: movement, vehicles, looting, inventory, hitscan and projectile weapons, healing, knock-downs and revives.
- A play zone shrinks in phases and damages anyone outside it.
- Squad voice and text chat, plus spectating a teammate after you die.
- After the match, placement, kills and damage feed into stats, ranked rating and rewards within a minute or two.
- Server-side replays of every match, so reported players can be reviewed and highlights can be rendered.
- Local input feels immediate: your own movement and firing show up on the next rendered frame, never after a network round trip.
- Server simulation at a steady 30 Hz, so the whole tick must finish inside 33 ms even with 100 live players.
- Playable on round trips up to about 150 ms and with 2 to 3% packet loss.
- The server is the only authority on position, health, ammo and hits. A modified client cannot grant itself anything.
- Fewer than 1 in 2,000 matches lost to a server crash.
- Platform services degrade separately: a broken stats service must not stop matches from starting.
Pin down tick rate, region list, and whether ranked play exists. Tick rate sets your CPU budget and bandwidth bill. The region list decides how many fleets you run. Ranked play means match results become valuable, so cheating and result tampering move up the priority list. It is also worth asking whether console and PC share matches, since that changes input handling and anti-cheat options.
03
Back-of-envelope numbers
Back-of-envelope numbers
Assume 3 million players online at the global peak, with about 87% of them in a match and the rest in menus. Matches last 30 minutes on average. The server runs at 30 Hz and the client sends input at the same rate.
| Quantity | Working | Result |
|---|---|---|
| Players in matches at peak | 3M x 0.87 | about 2.6M |
| Concurrent matches | 2.6M / 100 | about 26,000 |
| Match starts | 26,000 matches / 1,800 s each | about 14 per second |
| Players placed by matchmaking | 14 x 100 | about 1,400 per second |
| Snapshot size per client per tick | delta against last acked state, quantized | about 600 B |
| Downstream per player | 600 B x 30 ticks | 18 KB/s, about 144 kbps |
| Upstream per player | about 70 B of input x 30, with the last 3 inputs repeated | about 2 KB/s |
| Egress per match | 100 x 18 KB/s | 1.8 MB/s, about 14 Mbps |
| Fleet egress at peak | 26,000 x 14 Mbps | about 375 Gbps |
| Game hosts | 8 matches per 16-core host, plus 15% warm spare | about 3,750 hosts |
| Matches per day | average load about half of peak, 13,000 x 48 slots | about 620,000 |
| Replay storage | 620,000 x 30 MB compressed, kept 14 days | about 18.6 TB a day, 260 TB held |
The platform tier handles roughly 1,400 matchmaking placements a second, which is a normal web workload. The game tier is the expensive one: thousands of hosts, hundreds of gigabits of UDP, and a hard real-time deadline on every tick. Most of the design effort and most of the money go there.
A match is heaviest in its first five minutes, when all 100 players are alive and clustered near drop points. By minute twenty perhaps 25 remain. Packing hosts by average load will overrun ticks at match start, so pack by peak load per match, or stagger starts on each host so they do not all peak together.
04
APIs and the wire protocol
APIs and the wire protocol
There are two very different interfaces. Menus, parties and matchmaking talk to the platform over HTTPS and a WebSocket for push. Once a match is assigned, the client opens a custom UDP channel straight to the game server, and the platform is out of the loop until the match ends.
Platform calls
The in-match UDP channel
Every datagram carries a small header that gives you reliability only where you need it. The sender numbers its packets, and the receiver echoes back the newest sequence it saw plus a 32-bit mask of the ones before it. From that mask each side knows which packets landed without ever retransmitting the ones that did not.
State is sent unreliably, because a newer snapshot always replaces an older one. A handful of things must arrive exactly once, such as "you picked up the level 3 helmet" or chat. Those ride a reliable, ordered lane inside the same packets and are resent until acknowledged. You get TCP-like guarantees for the 5% of traffic that needs them and none of the stalls for the rest.
05
How the pieces fit
How the pieces fit
flowchart LR
C["Game client"] -->|HTTPS and WSS| GW["Platform gateway"]
GW --> PT["Party and presence"]
GW --> MM["Matchmaker"]
MM --> AL["Fleet allocator"]
AL --> FL["Regional host fleet"]
FL --> GS["Match server process"]
C <-->|UDP 30 Hz| RL["Relay or DDoS edge"]
RL <--> GS
GS -. "events" .-> K[["Kafka: match events"]]
GS -. "replay file" .-> OS[("Object storage")]
K --> ST["Stats and ranking"]
K --> AC["Anti-cheat analysis"]
ST --> DB[("Player DB")]
CDN["CDN"] -->|patches| CRead the diagram as two layers that touch in exactly one place. The platform layer on the left is a set of stateless services behind a gateway, deployed a few times a day, backed by a player database and Redis. The match layer on the right is a fleet of hosts, one per region, that run short-lived simulation processes. The allocator is the bridge: it takes a filled lobby from the matchmaker and returns the address of a process that is already warm and waiting.
Clients never connect to a match server's real address. Their UDP traffic goes through a relay edge that filters floods, hides host IPs, and can route over a private backbone when that beats the public internet. After the match, the server writes its replay to object storage, posts a signed summary, and streams per-event telemetry to Kafka, where stats, ranking and anti-cheat consume it independently.
Groups tickets by region, mode and skill band, and widens the band the longer a ticket waits. It works in short rounds of about two seconds per region, so it is a batch job that runs constantly, not a request handler.
Keeps a pool of pre-started, idle match processes on every host, with the map already loaded. Assigning a match is a state flip from ready to allocated, which takes milliseconds instead of the 20 seconds a cold start needs.
One process per match, pinned to a couple of cores. It owns all game state in memory, has no database on its tick path, and exits when the match ends. Its only outbound calls are fire-and-forget telemetry and the final summary.
Kafka decouples the 26,000 live servers from the slower consumers. If ranking is down for ten minutes, matches carry on and results simply land late. Nothing in the pipeline can block a tick.
06
Deep dive: making 100 players agree
Deep dive: making 100 players agree
No player ever sees the true current state. Your own character is predicted ahead of the server, everyone else is shown slightly in the past, and when you fire, the server rewinds the world to what you were looking at. Each technique hides latency in a different place, and each has a cost you should be able to name.
The server loop
The server does not react to packets as they arrive. It buffers them, and every 33.3 ms it runs one fixed step: apply each player's queued inputs in tick order, move vehicles and projectiles, resolve shots, tick the zone, then build and send a snapshot for each client. A fixed step makes the simulation deterministic for a given set of inputs, which is what lets clients run the same code for prediction and what makes replays possible.
flowchart TD
A["Drain UDP socket into per-player input queues"] --> B["Apply inputs for this tick"]
B --> C["Physics: players, vehicles, projectiles"]
C --> D["Validate shots with rewound hitboxes"]
D --> E["Zone damage, deaths, loot changes"]
E --> F["Update spatial grid"]
F --> G["Per client: pick relevant entities"]
G --> H["Delta against acked baseline, quantize, send"]
H --> I{"Time left in budget?"}
I -->|yes| J["Sleep until next tick"]
I -->|no| K["Record overrun, shed far updates next tick"]| Stage | Typical cost, 100 live players | Note |
|---|---|---|
| Receive and decode inputs | about 1.5 ms | batch reads with recvmmsg |
| Movement and physics | about 9 ms | vehicles dominate |
| Hit validation | about 3 ms | spikes in firefights |
| Game rules and zone | about 2 ms | cheap and steady |
| Relevance and snapshot building | about 9 ms | runs once per client |
| Send | about 2 ms | batched sendmmsg |
| Headroom | about 6.8 ms | kept free for spikes |
Prediction and reconciliation for your own player
When you press forward, the client does not wait. It applies the input locally using the same movement code the server runs, renders the result, and stores the input in a ring buffer tagged with its tick number. The input is also sent to the server.
Each snapshot that comes back says "this is where you were after input N". The client compares that with its own record for tick N. If they match, which is almost always, it drops older entries and carries on. If they differ, because you got hit by a vehicle you had not seen yet, the client snaps to the server's state and replays every stored input after N on top of it. Small errors are blended out over a few frames so the camera does not jerk. The player only ever notices a correction when something genuinely surprising happened.
Interpolation for everyone else
Other players cannot be predicted, because you do not have their inputs. Instead the client keeps the last few snapshots and renders remote entities at a point in time about two ticks behind the newest one, roughly 66 ms at 30 Hz, interpolating between the two snapshots that surround that moment. One lost packet then costs nothing visible, because there is still a later snapshot to blend towards. The price is that every enemy you see is slightly old, which is exactly what lag compensation has to correct.
Lag compensation: rewinding for the shot
flowchart LR
F["Client fires at tick 1042, interp delay 66 ms"] --> P["Packet reaches server at tick 1046"]
P --> R["Server computes the view time the shooter saw"]
R --> CAP{"Rewind under 200 ms?"}
CAP -->|no| CL["Clamp to 200 ms"]
CAP -->|yes| H["Restore hitboxes from history ring"]
CL --> H
H --> T["Trace the shot against old hitboxes"]
T --> V["Apply damage in the present tick"]
V --> N["Notify shooter and victim"]The server keeps a short history of every player's hitbox positions, one entry per tick for the last 200 ms or so. When a fire command arrives, it knows the shooter's round trip and interpolation delay, so it can work out which moment the shooter was actually looking at. It temporarily moves every other player's hitboxes back to that moment, traces the shot, restores them, and applies any damage in the present.
This is what makes aiming at a running target work on a 90 ms connection. It also creates the most common complaint in shooters: "I was already behind the wall". From the victim's point of view they reached cover, but on the shooter's screen they were still exposed, and the server sided with the shooter. Capping the rewind window keeps the effect bounded: a player on a 400 ms connection has to lead their shots instead of dragging everyone else into the past.
The player who steps around a corner sees the defender before the defender sees them, because the defender's view of the peeker is delayed by the peeker's upload time, the server tick and the defender's interpolation. Higher tick rates and lower interpolation delay shrink the gap. Nothing removes it. Say this out loud in the interview; it shows you know which latency can be engineered away and which cannot.
Area of interest: who needs to know about whom
Sending all 100 players to all 100 clients every tick is 10,000 entity updates per tick before you count loot, vehicles and projectiles. It is also a gift to wallhack authors, since the client would know where everyone is. Instead the server divides the 8 km map into a grid of 250 m cells, 32 by 32, and keeps each entity registered in its current cell.
For each client it collects entities from nearby cells and assigns a priority: distance, whether the entity is in the view cone, whether it just fired, and how long since that client last heard about it. Close and important entities go every tick. Distant ones go every third or fifth tick. Someone a kilometre away who is not shooting may not be sent at all. The packet has a byte budget, and the priority queue fills it until it is full. A scoped rifle extends the relevance range in the aimed direction so snipers still see their targets.
Delta compression and quantization
Each client acknowledges snapshots through the packet header, so the server knows the newest state that client definitely holds. The next snapshot encodes only what changed since that baseline: a bitmask per entity saying which fields follow, then the fields themselves. Positions are quantized to centimetres inside the map's bounds, angles to 16 bits, health to a byte. Together, relevance filtering, deltas and quantization take a naive 5 KB per client per tick down to the 600 bytes or so used in the estimates. If a client stops acking for a while, the server falls back to a full state for it rather than deltas against something the client may never have received.
07
Choices and their price
Choices and their price
The server decides every position, hit and pickup, and clients only send inputs. Cheats that edit memory to give infinite ammo or teleport simply have no effect. The cost is thousands of hosts and a CPU bill that grows with tick rate.
Required for competitive playCheap to run and fine for co-op games among friends. With 100 strangers, one host's connection decides the experience for everyone, and any client can lie about its own state. Hit detection trusted to clients is the quickest way to an aimbot epidemic.
Not for 100-player PvPAt 30 Hz a full lobby fits the tick budget on two cores. Once the player count drops below about 40, the server can double its rate for the final circles, where fights are closest and precision matters most, at no extra hardware cost.
Matches the load curveTactical shooters with 10 players run 64 to 128 Hz comfortably. Multiply that by ten players per match and 26,000 matches and you need several times the fleet and bandwidth, for a difference most players feel only in close fights.
For small lobbiesLost state is replaced by the next snapshot, so it never needs resending. Only events go on the reliable lane. A single lost datagram costs 33 ms of staleness at worst.
Industry standardOne lost segment holds back every later byte until it is retransmitted, so a 1% loss rate turns into regular freezes of a full round trip or more. Fine for the lobby and chat, wrong for movement.
Platform traffic onlyPlayers are spread fairly evenly over a fixed-size map, so a flat grid gives constant-time inserts and neighbour lookups with no rebalancing. It is about forty lines of code and trivially cache friendly.
Fits a bounded mapAdapts to dense hot spots, which helps when 30 players land in one town. It costs rebalancing work every tick as everyone moves. Worth it for open worlds with uneven density, rarely worth it here.
If profiling demands itOwn or reserve bare metal for the predictable daily baseline, and burst into cloud VMs in the same regions for weekends and launches. Both run the same container image with pre-started idle processes.
Cost plus elasticitySimplest to operate. At 375 Gbps of sustained egress, cloud data transfer pricing becomes one of the largest lines in the budget, and noisy neighbours cause tick jitter you cannot see or fix.
Good for launch, costly laterThe server already rejects impossible movement and limits what it sends. A client agent catches what the server cannot see, such as screen-reading aimbots and memory readers, and offline analysis of telemetry finds statistical outliers.
Layered defenceHarder to evade, but it runs with full system privileges on players' machines, raises real privacy and stability concerns, and is unavailable on some platforms. Many studios now use it; you should be able to argue either side.
Product decision08
Failure modes
Failure modes
A rare combination, say a vehicle exploding on a zone edge during a revive, hits a null pointer. Up to 100 players are dropped from a match they have invested twenty minutes in, and support tickets follow.
Crash dumps go straight to triage with the replay attached, so the exact inputs can reproduce the bug. Ship server builds to 1% of matches first and watch crash rate per build. Compensate affected players automatically. Mid-match failover sounds attractive but is rarely worth its complexity; prevention is cheaper.Forty players land in the same town, grenades and vehicle physics pile up, and the tick takes 45 ms instead of 33. The server falls behind real time and every client sees rubber-banding.
Measure tick time per stage and degrade gracefully: send far entities less often, simplify physics for vehicles nobody is near, cap projectiles. Keep the host packing conservative so one heavy match does not starve its neighbours on the same machine.A player on a busy Wi-Fi network loses 15% of packets for three seconds. Their inputs arrive in clumps and remote players freeze and jump on their screen.
Repeat recent inputs in every packet so a single loss is covered, let the server extrapolate a missing input briefly using the last one, and widen the client's interpolation buffer when it measures jitter. Show a network indicator so the player knows the problem is local.A new season goes live and peak concurrency jumps 60% in ten minutes. The login service and matchmaker are swamped, and the fleet runs out of warm processes.
Pre-scale the fleet the day before, put a login queue with an honest position counter in front of authentication, and let the matchmaker shed load by pausing low-priority modes. Patch downloads come from the CDN, never from origin.Someone who lost a ranked match floods the server address. Every match on that host lags out, which is exactly what the attacker wanted.
Never expose host IPs: clients talk to relay edges with scrubbing capacity, and a match can be moved to a different relay without changing the server. Validate the connection id on the first bytes of every packet and drop the rest cheaply.An aimbot reads the screen and moves the mouse. Its inputs are perfectly legal, just inhumanly accurate, so server validation passes them.
Stream aim telemetry to Kafka and score it offline: reaction times, snap angles, hit ratios through smoke. Flag outliers for review using the server replay, and ban in waves so cheat makers cannot tell which signal caught them.A hotfix changes recoil on the server, but some clients still run the old build. Prediction diverges on every shot and players see constant corrections.
Carry the build id in the matchmaking ticket and in the UDP handshake. The matchmaker only places a ticket on servers of the same protocol version, and the server refuses mismatched clients with a clear update prompt.09
How to handle the interview
How to handle the interview
Split the problem in two at the start. Say that platform services are a familiar web system and the match server is a real-time simulation, then spend most of your time on the second. Interviewers want to see that you know where the difficulty lives.
State the tick budget as a number. "30 Hz means 33 ms for everything" frames every later decision, from the relevance grid to how many matches fit on a host.
Explain prediction, interpolation and lag compensation as one story. Each hides latency somewhere different. Walk through a single shot from key press to damage, naming which clock each step uses.
Justify UDP with a failure, not a slogan. Describe what head-of-line blocking does to a movement stream under 1% loss, and then show that you still need a small reliable lane for events.
Do the bandwidth sum. Naive full-state broadcast is enormous; relevance, deltas and quantization bring it into a range a home connection handles. Showing the before and after is more convincing than naming the techniques.
Talk about the fleet, not just one server. Warm pools, packing by peak rather than average, and region selection by measured ping are what make "a server per match" work at 26,000 matches.
Treat cheating as a design input. Server authority, limited information to clients, and offline telemetry analysis each stop a different class of cheat. Mention the privacy trade-off of kernel drivers if asked.
Admit what you would not build. Mid-match server failover is a common trap. Saying it is too complex for its payoff, and that you would invest in crash prevention instead, reads as judgement.
10
From prototype to global fleet
From prototype to global fleet
A handful of hosts in one region, a matchmaker that fills lobbies first come first served, and a config file listing server addresses. Full-state snapshots are fine because nobody is measuring bandwidth yet. The goal is to find out whether the game is fun.
Delta compression, the relevance grid and lag compensation land because bandwidth and fairness complaints demand them. Results go to a stats database through a queue. Anti-cheat starts with server-side movement checks.
A fleet allocator with warm process pools replaces the static list, ping beacons let clients pick regions, and skill-based matchmaking with widening bands arrives. Relay edges go in front of servers after the first DDoS.
Reserved bare metal carries the baseline, cloud bursts cover peaks, and Kafka fans match events out to ranking, anti-cheat models and analytics. Server replays become the evidence store for bans and disputes.
The server raises its tick rate as players die, relay networks route over a private backbone, and capacity planning is driven by forecasts of each season's launch. The architecture barely changes from here; tuning does.
Watch and read
Further watching and reading
Try next
Free to read · better with landed
Whiteboard this with landed
landed runs it as a live system design round on the whiteboard and grades your trade-offs.