FiveM CreateThread and Wait(0) explained: why your script costs ms
How CreateThread, Wait and SetTimeout work in FiveM Lua, why Wait(0) in every loop shows up in resmon, and why a while loop without Wait freezes the game.
You write a loop to check something, and the game stops answering, or resmon shows your resource at a few ms while you stand still:
while true do
-- no Wait here: the game never gets control back
local ped = PlayerPedId()
endThis article explains how threads and Wait work in FiveM Lua, what Wait(0) really costs, and how to write loops that stay cheap.
How threads work in FiveM
Lua in FiveM is cooperative. Your script does not run in parallel with the game: it runs when the runtime calls it, and it must give control back. Two things make that possible:
- Event handlers and callbacks run once, from start to end, then return.
- Threads created with
CreateThreadare small coroutines that the scheduler resumes again and again. A thread gives control back when it callsWait.
CreateThread(function()
while true do
Wait(1000)
print('one second later')
end
end)CreateThread and Citizen.CreateThread are the same function, and so are Wait and Citizen.Wait. Use whichever you like, but be consistent in one script.
A while loop without Wait freezes the game
If a thread never calls Wait, the scheduler never gets control back. On the client the game stops drawing frames until the loop ends, and on the server every other script waits for yours.
CreateThread(function()
while true do
-- missing Wait: this blocks the whole client
end
end)The same happens with a repeat … until or a long for loop that does heavy work. Every loop that can run for a while needs a Wait inside it. If a script hangs the game on start, look for a loop like this first.
What Wait(0) costs
Wait(0) means: pause until the next frame (client) or the next tick (server), then continue. The loop runs again on every frame, so at 60 fps its body is paid for 60 times a second.
CreateThread(function()
while true do
Wait(0)
local coords = GetEntityCoords(PlayerPedId())
local dist = #(coords - vector3(215.0, -810.0, 30.7))
if dist < 3.0 then
-- draw a marker, show text...
end
end
end)One loop like this costs little. Ten of them, across thirty resources, add up to several ms of client time, which is exactly what resmon shows as high ms. The cost is not the Wait(0) itself, it is everything you repeat each frame.
Wait only as short as you need
Most checks do not need to run every frame. Ask what the loop is for:
| What the loop does | Reasonable wait |
|---|---|
| Draw a marker or text, handle a key press | 0 (only while the player is close) |
| Check a distance to decide if something is near | 250 to 1000 |
| Poll a state that changes rarely (job, vehicle) | 1000 or more, or use an event |
The usual trick is two speeds: a slow loop that checks the distance, and a fast one only while the player is inside the area.
local zone = vector3(215.0, -810.0, 30.7)
CreateThread(function()
while true do
local sleep = 1000
local dist = #(GetEntityCoords(PlayerPedId()) - zone)
if dist < 10.0 then
sleep = 0
DrawMarker(1, zone.x, zone.y, zone.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1.5, 1.5, 0.5, 255, 255, 255, 120, false, false, 2, false, nil, nil, false)
end
Wait(sleep)
end
end)Far from the zone, the thread wakes once per second. Near it, it draws every frame. Resmon drops to almost nothing for most players.
Tip: before you reach for a faster loop, ask whether an event or a state bag can tell you when the value changes. No loop at all is cheaper than any Wait.
SetTimeout: run something once, later
If you only need to do something once after a delay, you do not need a thread with a Wait:
SetTimeout(5000, function()
print('five seconds later')
end)SetTimeout(ms, callback) runs the callback once after the delay and does not block anything. It also exists on the server. For a repeating task a thread with Wait is the right tool, since SetTimeout does not repeat on its own.
Waiting inside event handlers
An event handler can call Wait too, because FiveM runs it in its own coroutine. Be careful on the server: after a Wait, the global source no longer points to the player who triggered the event. Save it first (see client and server events):
RegisterNetEvent('my_script:server:buy', function()
local src = source
Wait(500)
print(('player %s bought something'):format(src))
end)Stopping a thread
A thread ends when its function returns. To stop a loop from outside, use a flag the loop checks:
local running = true
CreateThread(function()
while running do
Wait(500)
-- work
end
end)
AddEventHandler('onResourceStop', function(resource)
if resource == GetCurrentResourceName() then
running = false
end
end)All threads of a resource stop on their own when the resource stops, so you only need this to stop a loop while the resource keeps running.
Checklist
| Symptom | Fix |
|---|---|
| Game freezes when a script starts | A while true do loop has no Wait: add one |
| Resource shows constant ms in resmon while idle | Replace Wait(0) with a distance check and a longer wait |
| Need to run something once after a delay | SetTimeout(ms, fn) instead of a thread |
source is wrong after a Wait on the server |
Save local src = source before the Wait |
| Polling a value that rarely changes | Use an event or a state bag instead of a loop |
Citizen.Wait or Wait? |
Same function, pick one style |
Quick answers
What does Wait(0) do in FiveM?
It pauses the current thread until the next frame on the client, or the next server tick. The loop then runs again on every frame, so everything inside it is paid for 60 or more times a second.
Is Citizen.Wait different from Wait?
No. Wait and Citizen.Wait are the same function, and so are CreateThread and Citizen.CreateThread. The short names are just aliases.
Does Wait stop the whole game?
No. It only pauses the thread that calls it, and the other threads and the game keep running. A loop that never calls Wait is what blocks everything.
Scripts that skip this problem
Advanced BoostingTablet-driven vehicle boosting: contracts from class D to S+, crews and a live queue.View script →
CCTV Security CamerasPlaceable cameras, a live multi-view tablet and printed evidence photos.View script →
Mic PhoneA foldable phone that unfolds into a tablet and carries onto a player's real phone.View script →