{}const=>[]async()letfn</>var
DevelopmentLua

How to optimize Lua code for games: 8 ways to speed up the game by 2-3 times

We analyze simple but effective code optimization techniques for game development projects. Local variables, caching, proper memory management, and other techniques that will help you squeeze out maximum performance without rewriting the entire project. Suitable for Love2D, Defold, Roblox and any other Lua engines.

К

Kodik

Author

5 min read

Why is optimization important in game development?

In games, every frame counts. If your code takes too long to execute, the game will start to lag — the FPS will drop, animations will twitch, and players will leave to write negative reviews. Optimization is especially critical in mobile games, where device resources are limited.

The good news is that Lua is already a fairly fast language, especially with the LuaJIT JIT compiler. But there are typical mistakes that can turn a fast game into a slideshow.

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

1. Local variables are your best friend

Problem: global variables in Lua run slower than local ones. Each time you access a global variable, the engine looks for it in a special table.

Bad:

function updatePlayer()
    player.x = player.x + speed * deltaTime
    player.y = player.y + gravity * deltaTime
end

Good:

local function updatePlayer()
    local px = player.x
    local py = player.y
    local spd = speed
    local grav = gravity
    local dt = deltaTime
    
    px = px + spd * dt
    py = py + grav * dt
    
    player.x = px
    player.y = py
end

Local variables are stored in registers or in a stack, access to them is much faster. This is especially important for functions that are called every frame (update, render).

2. Cache the calculation results

If a function returns the same value, do not call it again each time.

Bad:

function draw()
    for i = 1, #enemies do
        if distance(player, enemies[i]) < 100 then
            drawEnemy(enemies[i])
        end
    end
end

Good:

function draw()
    local enemyCount = #enemies
    local playerX, playerY = player.x, player.y
    
    for i = 1, enemyCount do
        local enemy = enemies[i]
        local dx = enemy.x - playerX
        local dy = enemy.y - playerY
        
        if dx*dx + dy*dy < 10000 then  -- избегаем sqrt
            drawEnemy(enemy)
        end
    end
end

Here we have hashed the number of enemies, the coordinates of the player and even avoided calling the distance function, replacing it with a comparison of squares of distances.

3. Avoid creating objects in loops

Creating new tables and rows is an expensive operation. The garbage collector then spends time cleaning them.

Bad:

function update()
    for i = 1, 1000 do
        local temp = {x = 0, y = 0}  -- каждый кадр 1000 новых таблиц!
        processData(temp)
    end
end

Good:

local tempData = {x = 0, y = 0}  -- создали один раз

function update()
    for i = 1, 1000 do
        tempData.x = 0
        tempData.y = 0
        processData(tempData)
    end
end

Or use object pools for frequently created entities (pools, particles, enemies).

4. Correct work with strings

Concatenation of strings through .. in loops is a classic error.

Bad:

local result = ""
for i = 1, 1000 do
    result = result .. tostring(i) .. ","  -- O(n²) сложность!
end

Good:

local parts = {}
for i = 1, 1000 do
    parts[i] = tostring(i)
end
local result = table.concat(parts, ",")  -- O(n) сложность

5. Cycle optimization

The order in which conditions are checked matters. Put the most likely conditions first.

Bad:

for i = 1, #entities do
    if entities[i].isDead and entities[i].isVisible and entities[i].isEnemy then
        removeEntity(entities[i])
    end
end

Good:

for i = 1, #entities do
    local entity = entities[i]
    if entity.isEnemy and entity.isVisible and entity.isDead then
        removeEntity(entity)
    end
end

If most entities are not enemies, checking isEnemy first will save checking the rest of the conditions.

6. Use built-in functions

Built-in Lua functions are optimized at the C level and run faster than your implementations.

Bad:

function findMax(arr)
    local max = arr[1]
    for i = 2, #arr do
        if arr[i] > max then
            max = arr[i]
        end
    end
    return max
end

Good:

local max = math.max(unpack(scores))  -- для небольших массивов

7. Profile the code

Don't optimize blindly! Use profilers to find real bottlenecks:

  • Love2D: built-in love.graphics.getStats()

  • Defold: built-in profiler

  • Roblox: MicroProfiler

It often turns out that the problem is not where you thought.

8. Practical tips for games

Spatial partitioning

Do not check collisions of all objects with all — this is O(n²). Use quadtrees or grids.

-- Вместо:
for i = 1, #bullets do
    for j = 1, #enemies do
        checkCollision(bullets[i], enemies[j])
    end
end

-- Используйте пространственную сетку
local grid = createGrid(mapWidth, mapHeight, cellSize)
-- проверяйте коллизии только в соседних ячейках

Lazy evaluation

Do not update objects that are not visible on the screen.

function updateEnemy(enemy)
    if not isOnScreen(enemy) and distanceToPlayer(enemy) > 500 then
        return  -- враг далеко и не виден — пропускаем
    end
    
    -- обычная логика обновления
end

Rendering batching

Draw similar objects with one call if the engine supports it.

-- Вместо 100 отдельных вызовов draw:
local batch = love.graphics.newSpriteBatch(texture, 100)
for i = 1, #sprites do
    batch:add(sprites[i].quad, sprites[i].x, sprites[i].y)
end
batch:draw()

When not to optimize

Remember the rule: Premature optimization is the root of all evil. First write working code, then measure performance, and only if there are problems — optimize bottlenecks.

Code readability is more important than micro-optimizations. If the game runs at 60 FPS, you don't need to squeeze 120 FPS out of it at the cost of code clarity.

Conclusion

Optimizing Lua code for games is a balance between performance and readability. Follow these simple rules:

  • Use local variables

  • Cache calculations

  • Avoid creating waste

  • Profile before optimization

  • Apply algorithmic optimization (spatial partitioning, batching)

With this knowledge, your games will run quickly and smoothly even on weak devices!

All this and much more can be mastered in Codice — our educational platform for beginner developers. We create easy-to-understand courses in Python, JavaScript, Lua, and other programming languages.

And we also have cool Telegram channel with a friendly developer community! Communicate with like-minded people, ask questions, share your projects and get advice from experienced programmers.

Join us — it's more fun to learn together!

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card