Game DevelopmentOct 30, 202510 min read

Lua Optimization Tips for FiveM Scripts

Performance tips and best practices for writing efficient Lua scripts that won't lag your server.

Alex Chen
Alex Chen
Lead Game Developer
Lua Optimization Tips for FiveM Scripts

Writing Performant FiveM Scripts

Lag kills roleplay servers. Here's how to write Lua that keeps your server running smooth.

The Golden Rules

  1. Never run expensive operations every frame
  2. Cache everything you can
  3. Use natives wisely
  4. Profile before you optimize

Thread Management

The biggest performance killer: threads with Wait(0) or no wait at all.

-- ❌ BAD: Runs every frame (1ms = 1000 calls per second)
CreateThread(function()
    while true do
        Wait(0)
        -- This runs 1000 times per second!
        local coords = GetEntityCoords(PlayerPedId())
    end
end)

-- ✅ GOOD: Runs every second (1000ms)
CreateThread(function()
    while true do
        Wait(1000)
        -- This runs once per second
        local coords = GetEntityCoords(PlayerPedId())
    end
end)

-- ✅ BETTER: Adaptive waiting
CreateThread(function()
    while true do
        local sleep = 1000
        local coords = GetEntityCoords(PlayerPedId())
        
        -- Only speed up when near something important
        if IsNearImportantLocation(coords) then
            sleep = 100
        end
        
        Wait(sleep)
    end
end)

Caching Strategies

Cache values that don't change often:

-- ❌ BAD: Getting ped every time
CreateThread(function()
    while true do
        Wait(0)
        local ped = PlayerPedId()  -- Called 1000x/sec
        local health = GetEntityHealth(ped)
    end
end)

-- ✅ GOOD: Cache the ped reference
local cachedPed = PlayerPedId()

CreateThread(function()
    while true do
        Wait(5000)
        cachedPed = PlayerPedId()  -- Update every 5 seconds
    end
end)

CreateThread(function()
    while true do
        Wait(100)
        local health = GetEntityHealth(cachedPed)
    end
end)

Table Operations

Lua tables are powerful but can be slow if misused:

-- ❌ BAD: Using table.insert in hot loops
local results = {}
for i = 1, 10000 do
    table.insert(results, someValue)
end

-- ✅ GOOD: Direct index assignment
local results = {}
for i = 1, 10000 do
    results[i] = someValue
end

-- ❌ BAD: Checking table length in loops
for i = 1, #largeTable do
    -- #largeTable is calculated each iteration
end

-- ✅ GOOD: Cache the length
local len = #largeTable
for i = 1, len do
    -- Much faster
end

Native Optimization

Some natives are expensive. Use them wisely:

-- Expensive natives (use sparingly):
-- GetClosestVehicle, GetClosestPed
-- GetGamePool
-- TaskWarpPedIntoVehicle (causes sync issues)

-- ❌ BAD: Finding closest vehicle every frame
CreateThread(function()
    while true do
        Wait(0)
        local vehicle = GetClosestVehicle(coords, 50.0, 0, 71)
    end
end)

-- ✅ GOOD: Use a zone-based approach
local nearbyVehicles = {}

CreateThread(function()
    while true do
        Wait(2000)
        nearbyVehicles = GetNearbyVehicles()  -- Custom efficient function
    end
end)

Event Optimization

Events are great, but can be abused:

-- ❌ BAD: Triggering events too frequently
CreateThread(function()
    while true do
        Wait(0)
        TriggerServerEvent('player:updatePosition', GetEntityCoords(ped))
    end
end)

-- ✅ GOOD: Throttle event frequency
local lastUpdate = 0
CreateThread(function()
    while true do
        Wait(100)
        local currentTime = GetGameTimer()
        if currentTime - lastUpdate > 5000 then
            TriggerServerEvent('player:updatePosition', GetEntityCoords(ped))
            lastUpdate = currentTime
        end
    end
end)

Memory Management

Lua has garbage collection, but you can help it:

-- ❌ BAD: Creating new tables constantly
CreateThread(function()
    while true do
        Wait(100)
        local data = {x = 0, y = 0, z = 0}  -- New table every 100ms
    end
end)

-- ✅ GOOD: Reuse tables
local data = {x = 0, y = 0, z = 0}
CreateThread(function()
    while true do
        Wait(100)
        data.x, data.y, data.z = table.unpack(GetEntityCoords(ped))
    end
end)

Profiling Your Code

Always measure before optimizing:

local function profile(name, fn)
    local start = GetGameTimer()
    fn()
    print(name .. ' took ' .. (GetGameTimer() - start) .. 'ms')
end

profile('ExpensiveOperation', function()
    -- Your code here
end)

Common Patterns

Distance Checks

-- ❌ BAD: Using native distance
local dist = #(coords1 - coords2)

-- ✅ GOOD: Squared distance (avoid sqrt)
local function distanceSquared(c1, c2)
    local dx, dy, dz = c1.x - c2.x, c1.y - c2.y, c1.z - c2.z
    return dx*dx + dy*dy + dz*dz
end

-- Then compare against squared threshold
if distanceSquared(coords1, coords2) < (5.0 * 5.0) then
    -- Within 5 units
end

The Optimization Checklist

  • [ ] No Wait(0) without good reason
  • [ ] Cached player ped and coordinates
  • [ ] Events are throttled appropriately
  • [ ] Tables are reused where possible
  • [ ] Expensive natives are minimized
  • [ ] Distance checks use squared values
  • [ ] Profiled and measured actual impact

Remember: premature optimization is the root of all evil. Measure first, then optimize the hot paths.

Need help optimizing your server?

Share this article:
Alex Chen
Alex Chen
Lead Game Developer

Passionate about building great digital experiences. When not coding, you can find me exploring new technologies and sharing knowledge with the community.

Related Articles

Continue reading with these related posts.

Need Help With Your Project?

Our team of experts is ready to help you build something amazing. Let's discuss your ideas and bring them to life.