PiTyUs.Hire me

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.

Updated 12 min readBy PiTyUs · FiveM developer

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.

The basic shapelua
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 valueRuns aboutUse for
Wait(0)Every frameDrawing markers/text, reading IsControlJustPressed, per-frame disables
Wait(100–250)4–10× a secondChecking state changes (vehicle, weapon)
Wait(500–1000)1–2× a secondDistance to locations, HUD values
Wait(5000+)Every few secondsHousekeeping, 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.

client.lualua
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:

client.lualua
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 frameRegisterKeyMapping — see commands and key mapping
Asking the server for a value every secondState bags or an event when it changes
Checking if the player entered an areaox_lib points/zones
Running something once laterSetTimeout(ms, fn)
SetTimeoutlua
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