Engineering
Optimising FiveM Lua scripts
Make FiveM Lua fast: dynamic Wait in loops, event-driven code instead of polling, vector distance with #(a - b), caching natives and values (ox_lib cache), backtick hashes, avoiding table and string garbage, batching database work, and measuring with resmon and the profiler.
Overview
Most slow FiveM scripts are not slow because Lua is slow. They are slow because they do work every frame that only needed doing once a second — or never. A handful of patterns fixes the majority of resmon numbers you will see.
Dynamic Wait
-- before: 0.10+ ms, runs every frame everywhere
CreateThread(function()
while true do
Wait(0)
if #(GetEntityCoords(PlayerPedId()) - SHOP) < 2.0 then
DrawText3D(SHOP, '[E] Shop')
end
end
end)
-- after: ~0.00 ms away from the shop
CreateThread(function()
while true do
local sleep = 1000
local dist = #(GetEntityCoords(cache.ped) - SHOP)
if dist < 20.0 then sleep = 250 end
if dist < 2.0 then
sleep = 0
DrawText3D(SHOP, '[E] Shop')
end
Wait(sleep)
end
end)Better still, use ox_lib points or zones, which do this bookkeeping for you — see zones. Thread basics: threads and Wait.
Events instead of polling
- React to
gameEventTriggered, framework job updates or state bag changes instead of checking every second. - Push data from the server when it changes instead of asking for it in a loop.
- Use state bag change handlers for synced values — see state bags.
Cheap habits in hot code
| Slower | Faster |
|---|---|
GetDistanceBetweenCoords(...) | #(a - b) with vectors |
GetHashKey('prop') in a loop | `prop` (compile-time) |
PlayerPedId() several times per tick | Once per tick, or cache.ped |
| Building tables every frame | Reuse a table or build once |
| String concatenation in loops | table.concat or build once |
| One DB write per change | Batch writes, save on interval/logout |
Server-side performance
- Loops over all players every tick → every few seconds, spread across ticks.
- Await database queries without blocking other work; add indexes — see database design.
- Avoid huge JSON encodes on every save.
- Hitches in the console point to long ticks — see profiler.
Measure
Open resmon 1 on the client and watch your resource while idle and while active. Aim for 0.00–0.02 ms idle. For server-side and deeper analysis, record with the profiler. Details: resmon explained.
Frequently asked questions
How do I get my FiveM script to 0.00 ms?
Sleep long when nothing is happening, only run every frame when drawing or reading input nearby, and use events instead of polling.
Is Wait(0) bad?
Only when it runs all the time. Use it when you must act every frame, and sleep otherwise.
What is the fastest distance check?
#(vectorA - vectorB) in Lua, without a native call.
How do I find what is slow?
resmon for per-resource time, then the profiler to see which functions take the time.
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.
- EngineeringThreads and Wait in FiveM: loops that do not eat framesCreateThread(fn) starts a coroutine that runs alongside the game; Wait(ms) pauses it and lets everything else run. Wait(0) resumes on the next frame, so the loop runs every frame (60+ times a second). Use it only while you must draw or read input every frame; otherwise sleep for hundreds of milliseconds, and make loops adaptive — fast when the player is near something, slow when they are not.
- EngineeringUsing the FiveM profiler to find slow codeRun profiler record 500 in the server console (or F8 on the client) to record about 500 frames, check progress with profiler status, then run profiler view to open the result in Chrome, or profiler saveJSON name.json to save it. In Chrome DevTools’ Performance tab, look for tall frames and hover the coloured blocks to see the resource, file and line.