How to find lag in a Roblox experience
The Glitchless team · November 6, 2026 · Roblox experiences, with Luau for LocalScripts and server Scripts
When players say a Roblox experience lags, they mean one of three things, and each has a different fix. Their own device can't draw frames fast enough. Your game server can't finish a frame's work in time, so its heartbeat drops and everyone in that server feels it at once. Or the connection between them is slow. Roblox caps the server heartbeat at 60 frames a second for every experience, so a server well under 60 is spending too long on your scripts or your physics.
Below: how to tell the three apart, how to measure each one in a play test and on a live server with plain Luau, the usual causes with their fixes, and how to collect it from every player and server so you know which map, device and update lag, in players rather than in one play test.
The short answer
- Whose lag it is: one player stuttering is their device or their connection; everyone in one server slowing down together is the server.
- Players' frame rate: time frames in a LocalScript with
RunService.PreRender. Report the median, the 95th percentile and the worst every few seconds; a frame over 50 ms is a stutter players feel. - Server heartbeat: time
RunService.Heartbeatin a server Script, or open Server Jobs in the Developer Console. It tops out at 60 a second, so a server at 30 is taking twice its budget per frame. - Ping: Server Stats in the Developer Console shows it. High ping with a healthy heartbeat is the connection; high ping with a low heartbeat is the server.
- Causes: heavy work every frame in scripts, too much unanchored physics, many instances made at once, draw calls on phones, remotes fired too often, memory that only grows, and assets still loading.
- Across players: the Creator Hub's performance dashboard charts averages and percentiles once you have 100 daily active users. To find which map, device or update lags, send each player's and each server's numbers somewhere you can split them, through your server, since only servers can make HTTP requests.
Whose lag is it?
Start from what players describe, and from how many of them describe it at once.
| What players see | Who's lagging | Where to look |
|---|---|---|
| One player's game stutters or runs slowly; others in the server are fine | That player's device | Frame times on the client, with render, GPU and physics time |
| Everyone in one server rubber-bands, hits land late, NPCs and doors freeze together | The server: its heartbeat dropped | Heartbeat on the server: Server Jobs in the Developer Console |
| One player teleports or acts late, but their frame rate is fine | Their connection | Ping: Server Stats in the Developer Console |
| Everyone's first minute is slow, then it settles | Loading and streaming | Time from joining to the first frame; instance streaming settings |
| A server gets slower the longer it runs | Memory that only grows on the server | Memory in the Developer Console, over an hour or more |
A slow server also shows up as ping: Roblox's own guide names higher latency as a symptom of a degraded heartbeat. So check the heartbeat before blaming players' connections.
Look at one session with Roblox's tools
Roblox's guide to finding performance problems puts it simply: you can watch the client, but you need tools to see the server. These are the ones it names, all for the session in front of you:
- Developer Console (F9 in a live game or a play test): the Server Jobs tab shows the server's Heartbeat steps per second, Server Stats shows average ping, and the Memory tab shows where memory goes, on the client and the server.
- MicroProfiler: what each frame spent its time on, on the client or the server. Look for frames over 16.67 ms, the budget at 60 fps.
- Performance Stats in the toolbar, and the Debug Stats overlays (Shift+F5 shows the frame rate summary on the client).
- Network Simulation in Studio, to play with added latency, jitter and packet loss before players do.
Measure in a real client, not only in Studio: Studio's own work skews its numbers, and a fast PC hides problems a phone can't keep up with. Roblox's guide suggests testing on lower-end hardware for exactly that reason.
Time players' frames
RunService.PreRender fires on the client before each frame is drawn, with the time since the last one. Keep those times for a few seconds, then report the median (how the game usually runs), the 95th percentile and the worst frame (what players complain about). Count frames over 50 ms separately: those are the stutters players notice, even when the average looks fine.
StarterPlayerScripts/FrameTimes.client.luau
-- Times every frame on this player's device and prints a summary every 5 seconds.
local RunService = game:GetService("RunService")
local WINDOW = 5 -- seconds per summary
local HITCH = 0.05 -- a frame over 50 ms is a stutter players feel
local frames, hitches = {}, 0
local started = os.clock()
RunService.PreRender:Connect(function(dt)
table.insert(frames, dt)
if dt > HITCH then
hitches += 1
end
local elapsed = os.clock() - started
if elapsed < WINDOW then
return
end
table.sort(frames)
local n = #frames
local function pick(p)
return frames[math.clamp(math.ceil(n * p), 1, n)] * 1000
end
print(string.format("%.0f fps, median %.1f ms, 95th %.1f ms, worst %.1f ms, %d stutters",
n / elapsed, pick(0.5), pick(0.95), frames[n] * 1000, hitches))
table.clear(frames)
hitches = 0
started = os.clock()
end)
Players' devices differ more than anything else here. The client is capped at 60 fps by default (Windows players can raise it to 240), so a PC reading 60 tells you little; a phone dropping to 25 in one map tells you a lot. Add what you suspect to the line you print, such as the number of parts near the player or the map, so a slow window comes with its context.
Time the server's heartbeat
On the server, RunService.Heartbeat fires once per server frame. Count them, and count the late ones: at 60 frames a second each frame has about 16.7 ms, so a frame over 33 ms took twice its budget. Print the players and memory alongside, because the most useful question about a slow server is what grew with it.
ServerScriptService/Heartbeat.server.luau
-- Counts the server's frames (its heartbeat) and the late ones, every 5 seconds.
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Stats = game:GetService("Stats")
local WINDOW = 5
local LATE = 1 / 30 -- twice the 60 Hz budget
local count, late, worst = 0, 0, 0
local started = os.clock()
RunService.Heartbeat:Connect(function(dt)
count += 1
if dt > LATE then
late += 1
end
worst = math.max(worst, dt)
end)
while true do
task.wait(WINDOW)
local elapsed = os.clock() - started
print(string.format("heartbeat %.1f/s, %d late frames, worst %.0f ms, %d players, %.0f MB",
count / elapsed, late, worst * 1000, #Players:GetPlayers(), Stats:GetTotalMemoryUsageMb()))
count, late, worst = 0, 0, 0
started = os.clock()
end
A healthy server stays close to 60. If it falls as the server fills up, the work grows with players: loops over every player each frame, characters' physics, remotes fired every frame. If it falls on one map or in one round, look at what that map spawns and simulates. Then run the MicroProfiler on the server during the slow part to see which work holds up the frame.
The usual causes, and their fixes
Roblox's guide to improving performance lists these, by where the time goes:
| Cause | What you see | Fix |
|---|---|---|
| Heavy work every frame in scripts | Heartbeat or frame rate that falls with how much is going on | Use RunService events sparingly; split big jobs across frames with task.wait(); move work that doesn't touch the data model to parallel Luau |
| Too much physics | Server heartbeat falls when many unanchored parts or constraints move | Anchor what doesn't need simulating, use adaptive stepping, box or hull collision on meshes, fewer constraints |
| Many instances made or destroyed at once | A stutter when a round starts or a map builds | Build large maps in chunks across frames; keep visual effects on the client |
| Draw calls and triangles | Low frame rate on phones and tablets only | Upload each mesh once and copy it in Studio, set RenderFidelity to Automatic or Performance, turn off CastShadow where it isn't needed, use instance streaming |
| Remotes fired too often | Ping and data rising with players; late hits | Send only what changed, throttle sends driven by input, keep tweens and effects on the client |
| Memory that only grows | A server or a long session that slows over time | Disconnect connections, clean up in PlayerRemoving, remove table entries you no longer need |
| Assets still loading | A slow first minute; stutters as new areas appear | Instance streaming, a lean ReplicatedFirst, preload only what the first view needs |
Change one thing, then measure the same way again. If the heartbeat or the stutter count didn't move, it wasn't the cause.
From one play test to every player
A play test shows the lag you can reproduce. Players hit the rest: on phones you don't own, in servers that filled up, on the map you didn't test after the last update. Once your experience has 100 daily active users, the Creator Hub's performance dashboard charts frame rate, server frame rate, memory and crash rate for the whole experience, split by platform, OS and place version, and its alerts can post to a webhook when one crosses a line. That tells you something got worse. Finding which map, device or update, and what happened in the game just before a slow stretch, needs each player's and each server's numbers somewhere you can split them.
Roblox only lets servers make HTTP requests (with HttpService, once Allow HTTP Requests is on in Game Settings → Security), and a server gets 500 of them a minute for everything outside Open Cloud. So each client sends its summary to the server over a RemoteEvent, the server checks it and batches it with its own, and sends the batch on.
Client summaries through the server
-- In a LocalScript: hand each 5-second summary to the server.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local report = ReplicatedStorage:WaitForChild("FrameReport")
report:FireServer({ fps = 58.2, p95 = 21.4, worst = 88.0, stutters = 2 })
-- In a server Script: check what arrives, keep it, send batches with HttpService.
local report = Instance.new("RemoteEvent")
report.Name = "FrameReport"
report.Parent = game:GetService("ReplicatedStorage")
local waiting = {}
report.OnServerEvent:Connect(function(player, summary)
if type(summary) ~= "table" or type(summary.fps) ~= "number" then
return -- a client can send anything: never trust it
end
table.insert(waiting, { fps = summary.fps, p95 = summary.p95, worst = summary.worst })
end)
Leave out the player's name and user ID: a summary needs the device, the map and the version, not who played. Roblox's error messages and script paths contain player names, so strip those too before anything leaves the server.
Every player's lag, and every server's, in one place
The Glitchless module for Roblox does all of this for you: it times every player's frames and names the likely cause of each stutter (assets still loading, a burst of new instances, physics, rendering or scripts), sends each server's heartbeat, late frames, players and memory, and each player's ping and loading time, through your server and without names or user IDs. Frame times merge across players, so you can split them by map, device, server and version, in players: how many lagged, and how that compares with the usual.
Your coding agent reads the same over MCP. glitchless_triage ranks what to fix first, with the evidence. glitchless_performance breaks frame times and stutter causes down by level, device and version. glitchless_servers reads each server's frame time against its budget by how many players were in it, and glitchless_match opens one server from every side, so the agent can tell a whole server lagging from one player's connection. After a fix, glitchless_compare_versions checks the new version with real players.
Ask your coding agent
Use Glitchless to find where my Roblox experience lags. Start with glitchless_triage. For the worst map, check players' frame times and stutter causes by device with glitchless_performance, and the servers' heartbeat by player count with glitchless_servers. Tell me whether it's players' devices, the server or connections, find the cause in the code, fix it, and after I publish, compare the new version with glitchless_compare_versions.
Frequently asked questions
What is server heartbeat in Roblox?
The number of frames a game server finishes each second. Roblox caps it at 60 for every experience, so a lower number means the server needs more than a sixtieth of a second for a frame's work. Every player in that server feels it at once, as rubber-banding, late hits and higher ping.
Why does my game lag on phones but not in Studio?
Studio runs on your computer, which hides work a phone can't keep up with, and Studio's own work skews its numbers. Draw calls, triangles and shadows cost far more on a phone. Test on a low-end phone, and look at frame times split by device.
Is it lag or ping?
Low frame rate on one device is that device. High ping with a healthy server heartbeat is the connection. High ping for everyone in a server, with the heartbeat under 60, is the server: fix the server first.
Can a LocalScript send data to an analytics service?
No. Only server scripts can make HTTP requests, after you turn on Allow HTTP Requests. A client passes its data to the server over a RemoteEvent, and the server sends it on.
Sources
- Roblox: Identify performance issues
- Roblox: Improve performance
- Roblox: Performance dashboard
- Roblox: Alerts
- Roblox: RunService (engine reference)
- Roblox: Stats (engine reference)
- Roblox: HTTP service
Something wrong or out of date? Email support@glitchless.gg and we'll fix it. Glitchless isn't affiliated with Roblox Corporation; product names are their owners' trademarks.