The replication model
Roblox uses a server-authoritative model. The server owns the canonical game state, and clients receive replicated copies. When a client wants to change something, it sends a request to the server, the server validates and applies it, and the change propagates to all clients.
In peer-to-peer games, clients can modify shared state directly. Roblox's server authority prevents many forms of cheating and adds latency to player actions.
What gets replicated automatically
Roblox automatically replicates changes to:
- Instance properties (Position, Size, Color, etc.)
- Instance hierarchy (Parent changes, new instances)
- Humanoid state (health, walk speed, jump)
- Physics simulation results for owned parts
Automatic replication is convenient and expensive. Every property change generates a replication packet. If you change 500 part colors every frame, that is 500 property changes replicated to every connected client, every frame.
-- 100 property changes replicated per frame
RunService.Heartbeat:Connect(function()
for _, part in workspace.Effects:GetChildren() do
part.Color = Color3.fromHSV(tick() % 1, 1, 1)
part.Transparency = math.sin(tick()) * 0.5 + 0.5
end
end)Remote events and remote functions
| Feature | RemoteEvent | RemoteFunction |
|---|---|---|
| Direction | Fire and forget | Request/response |
| Blocking | No | Yes (caller waits) |
| Return value | None | Server returns data |
| Failure mode | Silent drop | Timeout/error |
| Use case | Actions, updates | Data queries |
RemoteFunction needs care. If the server calls a RemoteFunction that invokes the client, a malicious client can simply never respond. The server thread hangs while waiting for the reply. Use RemoteEvents for server-to-client communication.
-- Server waits for client response
local result = someRemoteFunction:InvokeClient(player, data)
-- If client never responds, this thread is permanently stuck
-- Fire and forget, handle response via separate event
someRemoteEvent:FireClient(player, data)
-- Client fires back when ready
responseEvent.OnServerEvent:Connect(function(player, response)
handle_response(player, response)
end)Bandwidth optimization
Every RemoteEvent:FireServer() call has overhead: headers, serialization, and routing. Sending 60 tiny events per second is worse than sending 10 batched events per second with the same total data.
-- One event per input sample
UserInputService.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
aimRemote:FireServer(input.Position)
end
end)
-- Batch inputs and send at fixed rate
local pending_aim = nil
UserInputService.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
pending_aim = input.Position
end
end)
RunService.Heartbeat:Connect(function()
if pending_aim then
aimRemote:FireServer(pending_aim)
pending_aim = nil
end
end)Data serialization costs
Serialization size varies by data type:
| Type | Approximate bytes | Notes |
|---|---|---|
| boolean | 1-2 | Cheapest |
| number | 8 | Always 64-bit float |
| string | 2 + length | Length prefix + UTF-8 |
| Vector3 | 12 | Three 32-bit floats |
| CFrame | 24-48 | Compressed rotation |
| table | varies | Recursive, can be huge |
| Instance | 4-8 | Reference ID |
Sending an Instance reference is much cheaper than sending its properties. If both sides can access the instance, send the reference and let the other side read what it needs.
Network ownership
Physics-simulated parts have a network owner: either the server or a specific client. The owner runs the physics simulation and replicates results to everyone else.
By default, the server owns everything. When a player's character touches a part, Roblox may automatically transfer ownership to that player's client to reduce perceived latency.
You can manually control this:
part:SetNetworkOwner(player) -- client simulates
part:SetNetworkOwner(nil) -- server simulates
part:SetNetworkOwnershipAuto() -- Roblox decidesClient-owned physics feels more responsive because the player sees instant results without round-tripping to the server. It also gives the client control of the simulation. Use server ownership for authoritative objects such as projectiles, objectives, and competitive mechanics.
Designing for scale
For games with 50+ players in one server, the network becomes the primary bottleneck. The total outbound bandwidth scales with players * changes_per_frame * data_per_change. With 100 players and 1000 replicated properties changing per frame, you're pushing significant data.
Strategies that help at scale:
- Use
StreamingEnabledto only replicate nearby instances - Batch remote events and send at 10-20 Hz instead of every frame
- Use numeric IDs instead of string keys in remote event data
- Replicate deltas (what changed) instead of full state
- Put infrequently-changing data in attributes/values that only replicate on change rather than polling
