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:

lua
while true do
    -- no Wait here: the game never gets control back
    local ped = PlayerPedId()
end

This 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 CreateThread are small coroutines that the scheduler resumes again and again. A thread gives control back when it calls Wait.
lua
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.

lua
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.

lua
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.

lua
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:

lua
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):

lua
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:

lua
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 →

Keep reading