Engineering
Threads and Wait in FiveM: loops that do not eat frames
How CreateThread and Wait work in FiveM Lua, why Wait(0) runs every frame, how to write adaptive loops that sleep when nothing is nearby, SetTimeout, and the patterns that keep resmon near 0.00 ms.
Overview
Almost every FiveM performance problem comes back to one construct: a while true do loop with Wait(0) inside a thread. It is the right tool for drawing things every frame and the wrong tool for nearly everything else. Understanding what Wait actually does — and how to make a loop sleep when it has nothing to do — is the single highest-value habit a FiveM developer can build.
What a thread is in FiveM
FiveM Lua threads are coroutines managed by the runtime’s scheduler. They do not run in parallel on other CPU cores; they take turns. When a thread calls Wait, it hands control back and asks to be resumed after that many milliseconds (or next frame for Wait(0)). A thread that never waits never hands control back — the game hangs.
CreateThread(function()
while true do
-- do something
Wait(1000) -- then sleep one second
end
end)What Wait(0) really costs
At 60 frames per second, a Wait(0) loop runs 60 times a second; at 144 fps, 144 times. Whatever is inside — natives, distance checks, table walks — is multiplied by that. One such loop is fine; twenty of them, each checking a dozen markers, is how a client ends up with dozens of milliseconds of script time per frame and visible stutter.
| Wait value | Runs about | Use for |
|---|---|---|
Wait(0) | Every frame | Drawing markers/text, reading IsControlJustPressed, per-frame disables |
Wait(100–250) | 4–10× a second | Checking state changes (vehicle, weapon) |
Wait(500–1000) | 1–2× a second | Distance to locations, HUD values |
Wait(5000+) | Every few seconds | Housekeeping, periodic syncs |
01The adaptive loop
The standard pattern: sleep long by default, and only drop to Wait(0) while the player is close enough to need per-frame work.
local SHOP = vector3(25.7, -1347.3, 29.5)
CreateThread(function()
while true do
local sleep = 1000
local dist = #(GetEntityCoords(PlayerPedId()) - SHOP)
if dist < 15.0 then
sleep = 0
DrawMarker(2, SHOP.x, SHOP.y, SHOP.z, 0, 0, 0, 0, 0, 0, 0.3, 0.3, 0.3, 255, 255, 255, 180, false, true, 2, false, nil, nil, false)
if dist < 1.5 and IsControlJustPressed(0, 38) then
-- open the shop
end
end
Wait(sleep)
end
end)Far away, this costs one distance check per second. Near the shop it runs every frame, because drawing a marker and reading a key press must. That single change often takes a resource from 0.5 ms to 0.01 ms in resmon.
Many locations: one loop, not many
Starting one thread per location multiplies the overhead. Keep one loop that checks all locations, and use the closest distance to decide how long to sleep:
CreateThread(function()
while true do
local pos = GetEntityCoords(PlayerPedId())
local closest = math.huge
for i = 1, #Config.Shops do
local d = #(pos - Config.Shops[i].coords)
if d < closest then closest = d end
if d < 15.0 then
-- draw this shop's marker
end
end
Wait(closest < 15.0 and 0 or math.min(2000, math.floor(closest * 10)))
end
end)Things that should not be loops
| Instead of polling… | Use |
|---|---|
| Checking a key every frame | RegisterKeyMapping — see commands and key mapping |
| Asking the server for a value every second | State bags or an event when it changes |
| Checking if the player entered an area | ox_lib points/zones |
| Running something once later | SetTimeout(ms, fn) |
SetTimeout(5000, function()
print('five seconds later, once')
end)Measuring the result
Open F8 and run resmon 1. Your resource’s CPU time should sit near 0.00–0.02 ms when the player is nowhere near anything it handles. How to read the monitor is in resmon and resource time.
Frequently asked questions
What does Wait(0) mean in FiveM?
Pause this thread and resume it on the next frame. A loop with Wait(0) therefore runs every frame.
Is Citizen.CreateThread different from CreateThread?
No. CreateThread and Wait are shorter aliases for Citizen.CreateThread and Citizen.Wait.
Why does my game freeze when I start my script?
A while true do loop has no Wait inside, so the thread never gives control back. Add a Wait.
How do I make a loop use less CPU?
Sleep longer when there is nothing to do: check distance once a second and only use Wait(0) while the player is close.
Are FiveM threads real multithreading?
No. They are coroutines that take turns on the same thread; they only help if each one waits.
Need this built, not just explained?
Ten years of FiveM work, from Lua to NUI
Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.
Related guides
- Engineeringresmon: finding the resource that is eating your framesPress F8 and type resmon 1 (or resmon true) to open the client resource monitor. The CPU msec column is how many milliseconds per frame each resource uses; idle resources should sit near 0.00–0.02 ms, and anything consistently above about 0.5 ms deserves a look. Sort by CPU, reproduce the situation where it lags, and inspect the worst resource’s loops.
- FrameworksCreating zones in FiveM with ox_lib (and PolyZone)With ox_lib, create zones with lib.zones.box, lib.zones.sphere or lib.zones.poly and handle onEnter, onExit and inside. Use the restricted /zone poly|box|sphere command to draw zones in game; ox_lib saves them to created_zones.lua. PolyZone is the older library many QBCore resources still use. Zones run on the client — confirm anything important on the server.
- EngineeringCommands and rebindable keys in FiveMRegister a command with RegisterCommand(name, handler, restricted). Setting restricted to true on the server requires the ACE command.name. Bind a client command to a rebindable key with RegisterKeyMapping(command, description, 'keyboard', 'F5'), which adds it to the game’s Key Bindings settings. Use +name/-name commands for keys that act while held.